Why does this use of the detection idiom result in different compilation errors for Clang and GCC and none for MSVC

While playing around with the detection idiom and void_t I found that expressions where the subtraction operator and void* are involved lead to different errors in the tested compilers.

GCC 10.2 and below, no matter if C++11,14,17 or 20 is used, seem to treat some expressions as hard error rather then substitution failures.

I’m working with the following (striped down) example

#include <type_traits>

template< class, class, class = void >
struct has_minus_void_t : std::false_type {};
template<class T, class U>
struct has_minus_void_t<T, U, std::void_t<
    decltype(T() - U())
    >> : std::true_type {};

static_assert(has_minus_void_t<int*,int*>::value, "int*");
static_assert(!has_minus_void_t<int*,void*>::value, "intvoid*");
static_assert(!has_minus_void_t<void*,int*>::value, "voidint*");
static_assert(!has_minus_void_t<void*,void*>::value, "void*");

and would expect the code to compile without errors for all four language standards (contains a std::void_t replacement for pre C++17 so more standards can be tested) but

  • GCC 10.2 and below complain about the last two asserts
  • Clang doesn’t complain
  • MSVC complains about the int*/void* combination

I tried to search for an existing bug report but couldn’t find one.

I’m wondering which compiler is right and why.

Here’s a godbolt.

Answer

GCC 10.2 and earlier are wrong to treat (void*)0 - (void*)0 as a hard instead of soft error. This is fixed in trunk.

Clang does everything correctly.

MSVC is wrong to allow (int*)0 - (void*)0; (while correctly rejecting (void*)0 - (int*)0;), as shown by:

[expr.add]/2.2

For subtraction, one of the following shall hold: …

— both operands are pointers to cv-qualified or cv-unqualified versions of the same completely-defined object type; …