Ebisu Dev Ebisu Dev - 2 months ago 14
C++ Question

Wrong output for SFINAE member detect in MSVC2015

I was learning about SFINAE and how to easy implement it with

void_t
. But I get different output for different compilers:

//pre c++17 void_t definition:
template<class... Ts> struct make_void {typedef void type;};
template<class... Ts> using void_t = typename make_void<Ts...>::type;

//check for member helper structures
template<class, class = void>
struct has_abc : std::false_type
{ };

template<class T>
struct has_abc<T, void_t<decltype(T::abc)>> : std::true_type
{ };

class has
{
public:
void abc();
};

class has_not
{ };

int main()
{
std::cout << has_abc<has>::value << std::endl;
std::cout << has_abc<has_not>::value << std::endl;
}


GCC 5.3.0 prints expected output
1 0
, but MSVC 2015 prints
0 0
, why?




EDIT:

Additional example with working GCC 5.3.0 code that supposedly violates c++ syntax:

template<class T>
void test()
{
std::cout << std::is_same<decltype(T::func), void(T::*)(void)>::value << std::endl;
}

class Test
{
public:
void func();
};

int main()
{
test<Test>();
}


Output:

1

Answer

In fact there is an error with your code. MSVC is right while GCC is simply wrong.

The syntax for pointer to member function doesn't work like that. You have to put the & in front of the expression:

//check for member helper structures
template<class, class = void>
struct has_abc : std::false_type {};

template<class T>
struct has_abc<T, void_t<decltype(&T::abc)>> : std::true_type {};
//     Notice the '&' there ------^

The syntax for T::member only work with static data member, and typename T::member work for member types. While working with sfinae, it's important to distinguish small syntax properties and differences.

As a request in the comment, here's multiple statement that shows that non static member cannot be referenced without the & with GCC 5.3: https://godbolt.org/g/SwmtG2

Here's an example with GCC: http://coliru.stacked-crooked.com/a/0ee57c2c34b32753

Here's an example with MSVC: http://rextester.com/FJH22266

Here's the section from the C++ standard that explicitly state that without the &, the expression is not well formed:

[expr.prim.id]/2

An id-expression that denotes a non-static data member or non-static member function of a class can only be used:

  • as part of a class member access ([expr.ref]) in which the object expression refers to the member's class or a class derived from that class,

  • or to form a pointer to member ([expr.unary.op]), or

  • if that id-expression denotes a non-static data member and it appears in an unevaluated operand. [ Example:

-

 struct S {
   int m;
 };

 int i = sizeof(S::m);           // OK
 int j = sizeof(S::m + 42);      // OK

— end example ]


As we discussed in the chat, we concluded that the reason there is a difference with the two compilers is that both GCC and MSVC has bugs that prevent this code to work well. As mentionned, MSVC will refuse to apply the SFINAE rule if there is an unrelated class that don't implement the rule correctly: http://rextester.com/FGLF68000

Note that sometime, changing the name of a type trait helped MSVC to parse my code correctly, but it's mostly unreliable.

Consider reporting a bug to microsoft and upgrade your GCC version if you want your code to work as expected.