在函数模板中转换为否定的一元表达式失败

问题描述

我使用以下代码来比较算术类型。类模板 equal 比较给定的参数是否相等并将结果存储在成员变量 result 中。类模板还为 bool 提供了一个强制转换运算符,以便在条件语句中进行评估。我还提供了模板推导指南,以便在构造 equal 对象时不必手动转换参数。当 equal 从非模板化函数中求值时,这始终有效,但当表达式被 operator!() 否定时,无法在函数模板中编译,至少在当前的 clang 编译器 (11.0.1) 上是这样。但是,它使用 gcc 和 msvc 进行编译。在代码示例中,函数模板dummy()中的编译失败:

#include <algorithm>
#include <concepts>
#include <limits>
#include <type_traits>

template<typename T>
inline constexpr auto abs(T value) -> T
{
    if (value < 0)
        return -1 * value;
    return value;
}


template<std::integral T>
inline constexpr auto integersEqual(const T lref,const T rref) noexcept -> bool
{
    return lref == rref;
}

template<std::floating_point T>
inline constexpr auto floatingPointsEqual(const T lref,const T rref) noexcept -> bool
{
    constexpr T epsilon = std::numeric_limits<T>::epsilon() * 1000;
    return abs(lref - rref) <=
           (epsilon * std::max<T>(abs(lref),abs(rref)));
}

template<typename T>
inline constexpr auto arithmeticEqual(const T lref,const T rref) noexcept -> bool
{
    if constexpr (std::is_integral_v<T>)
    {
        return integersEqual(lref,rref);
    }
    else
    {
        return floatingPointsEqual(lref,rref);
    }
}

template<typename T>
class equal final
{
public:
    inline constexpr equal(const T lref,const T rref) noexcept : result(arithmeticEqual(lref,rref)) {}
    inline constexpr operator bool() const noexcept
    {
        return result;
    }
    inline constexpr auto operator!() const noexcept -> bool
    {
        return !result;
    }

private:
    const bool result;
};

// deduction guides for equal
template<typename T1,typename T2>
equal(T1,T2) -> equal<std::common_type_t<std::decay_t<T1>,std::decay_t<T2>>>;

template<typename T1,typename T2>
auto dummy(const T1 t1,const T2 t2) -> bool
{
    return !equal(t1,t2);                                                            // <- This line fails. If not negated,it works
    //return equal(t1,t2) == false;                                                  // this works
    //return !equal<std::common_type_t<std::decay_t<T1>,std::decay_t<T2>>>(t1,t2);  // this works as well
}


auto main() -> int
{
    return dummy(12,13);
}

链接到编译器资源管理器:https://godbolt.org/z/PMcMab

编译失败并显示以下错误消息:

<source>:67:12: error: invalid argument type 'equal' to unary expression
    return !equal(t1,t2); 

operator!() 添加到类模板 equal 并不能解决此问题。我的问题是:为什么 clang 不接受这个代码?我看不出这段代码有问题。这是clang中的错误吗?通常 clang 比其他编译器更严格地遵循标准,所以我想知道 gcc 和 msvc 是否都接受此代码但真的不应该。我怀疑模板参数推导是罪魁祸首,因为提供具体的模板参数可以解决问题。扣除是否由于某种我不知道的原因而失败?

解决方法

我有点认为这是 clang 中的一个错误。我摆弄了一段时间。

我试过了:

return equal( t1,t2 ).operator !();

哪个 clang 不喜欢,但其他编译器喜欢。

Clang 说:

<source>:69:27: error: member reference base type 'equal' is not a structure or union
    return equal( t1,t2 ).operator !();

一个有趣的错误。

如果你只是这样做:

bool dummy2( int t1,unsigned t2)
{
    return !equal(t1,t2);
}

它编译得很好。所以它与推导指南结合在模板方法中调用推导指南有关。

最后编译:

template<typename T1,typename T2>
auto dummy(const T1 t1,const T2 t2) -> bool
{
    typedef decltype( equal(t1,t2) ) _TyEqual;
    static_assert( std::is_same_v< _TyEqual,equal< std::common_type_t<std::decay_t<T1>,std::decay_t<T2>>>> );
    return !_TyEqual(t1,t2);
}

再加上其他错误,我认为错误是在应用演绎指南时,clang 使用了某种中间类型。一旦你把类型拼出来,它就会喜欢它......

无论如何,有趣的问题,我在这个过程中获得了一些关于演绎指南的知识。