I use the following code to compare arithmetic types. The class template equal
compares the given arguments for equality and stores the result in the member variable result
. The class template also provides a cast operator to bool
in order to be evaluated in conditional statements. I also provide a template deduction guide in order to not have to cast the arguments manually when constructing an equal
object. This works always when equal
is evaluated from non-templated functions, but fails to compile in function templates when the expression gets negated by operator!()
, at least on the current clang compiler (11.0.1). However, it compiles with gcc and msvc. In the code example, the compilation fails in the function template 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);
}
(Link to compiler explorer: https://godbolt.org/z/PMcMab)
Compilation fails with the following error message:
<source>:67:12: error: invalid argument type 'equal' to unary expression
return !equal(t1, t2);
Adding the operator!()
to the class template equal
does not solve this issue. My question is: why doesn't clang accept this code? I don't see a problem with this code. Is this a bug in clang? Usually clang follows the standard more strictly than the other compilers, so I was wondering if both gcc and msvc are accepting this code but really shouldn't. I am suspecting the template argument deduction to be the culprit, since providing a concrete template arguments resolves the issue. Does the deduction fail for some reason I am not aware of?
I kinda think it is a bug in clang. I fiddled around with this for a while.
I tried:
return equal( t1, t2 ).operator !();
which clang didn't like, but the other compilers did.
Clang said:
<source>:69:27: error: member reference base type 'equal' is not a structure or union
return equal( t1, t2 ).operator !();
an interesting error.
Also if you just do this:
bool dummy2( int t1, unsigned t2)
{
return !equal(t1, t2);
}
it compiles fine. So it has something to do with the deduction guide in combination with the invocation of the deduction guide inside of a template method.
Finally, this compiles:
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);
}
which coupled with the other errors makes me think that the error is that clang uses some intermediate type of some sort when applying the deduction guide. Once you spell out the type to clang, it likes it...
Anyway, interesting problem and I gained a bit of knowledge about deduction guides in the process.