m.s.
m.s.

Reputation: 16344

Clang vs. GCC: Error in unevaluated context breaks SFINAE

While continuing work from my previous question, I came across different behavior of clang and GCC.
I need to check the member function pointer because I need to know if the function is inherited or not.

When comparing member function pointers in an SFINAE context, member function Foo::foo() exists, but its body contains code (x.hello()) which eventually does not compile.

The following code compiles with clang. GCC however seems to evaluate the function body of Foo::foo() and exits with an error ('struct Caller' has no member named 'hello'), despite being in an unevaluated SFINAE context (or so I hope).

#include <iostream>
#include <type_traits>

struct Foo
{
    template <typename T> void foo(T&& x) { x.hello(); }
};

struct Caller
{    
    template <typename T>
    auto call(T&& x) -> decltype(
        std::enable_if_t<
            std::is_same<
                decltype(&T::template foo<decltype(*this)>),
                void (T::*)(decltype(*this))
            >::value
        >())
    {
        //x.foo(*this);
    }
};

int main()
{
  Caller c;
  c.call(Foo());
}

I tested with:

Compiler options for both: -std=c++14 -O2 -Wall -pedantic -pthread

live example

My questions:

  1. who is right? Clang or GCC?
  2. How can I get the code to compile with GCC?

Upvotes: 3

Views: 181

Answers (1)

Niall
Niall

Reputation: 30604

In answer to the second question

How can I get the code to compile with GCC?

I would simplify the expression being passed to the decltype(). If the compilation of the call method is dependent on the compilation of x.foo(*this);, then that is what you should use.

struct Foo
{
    template <typename T> void foo(T&& x) { x.hello(); }
};

struct Caller
{    
    template <typename T>
    auto call(T&& x, int) -> decltype(x.foo(*this))
    {
        //x.foo(*this);
    }
    template <typename T>
    void call(T&&, char){ std::cout << "hello" << std::endl;}
};

int main()
{
  Caller c;
  c.call(Foo(), 0);
}

Demo here.


I think the OP's issue with gcc lies in the address being taken of the function (or decaying to a function pointer if not explicitly taken). I think this is something of a corner case in the standard. The x.hello() would need to exist (compile) if the method Foo::foo is required; in general taking the address of something satisfies that, but in an unevaluated context (decltype()), I'm not sure if that would apply - certainly clang does not require it to exist (nor does MSVC).

In that respect,

Who is right? Clang or GCC?

I suspect clang implements a more permissive reading of the standard and probably more correct reading. The operand to a decltype() is an unevaluated operand, see [dcl.type.simple]/4;

The operand of the decltype specifier is an unevaluated operand (Clause [expr]).

Upvotes: 2

Related Questions