3

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:

  • clang 3.8.0
  • g++ 6.1.0

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?
Community
  • 1
  • 1
m.s.
  • 16,063
  • 7
  • 53
  • 88
  • 3
    How about mentioning the versions and options of the compilers? `x.hello()` is definitely not in a SFINAE context. – Marc Glisse Jul 14 '16 at 10:44

1 Answers1

2

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]).

Niall
  • 30,036
  • 10
  • 99
  • 142
  • I need to check the member function pointer because I want to know if the function is inherited or not (see my [linked previous question](http://stackoverflow.com/a/38347040/678093)). – m.s. Jul 14 '16 at 11:34
  • @Niall I am struggling to understand why that works. `Caller` doesn't have a `hello` method. How can `void Foo::foo(Caller&&)` be instantiated? – Jonathan Mee Jul 14 '16 at 11:36
  • 1
    @JonathanMee. Correct, that is why the substitution fails, and then the method removed from the overload resolution. W.r.t the OP code, I think the issue lies in the address being taken of the function. I think this is something of a corner case in the standard. The `x.hello` would need to exist (compile) if the method 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. – Niall Jul 14 '16 at 11:41
  • @Niall but if the taking the address requires `x.hello` to be compiled, and this fails, why isn't the char overload chosen here: http://coliru.stacked-crooked.com/a/6506b9f917f89623 – m.s. Jul 14 '16 at 11:43
  • @Niall So you're saying that `decltype` *doesn't* require the method to compile? (I mean that is how it's working on all the compilers, but my mind is a little blown by that.) – Jonathan Mee Jul 14 '16 at 11:49
  • 1
    @m.s. At that point, it is a hard error - the address is deemed to be required (because of the `&`) by gcc; clang on the other hand seems to discard the required existence the pointer, so the error is not a hard one. – Niall Jul 14 '16 at 11:54
  • @JonathanMee. It would require the expression to compile, but since the substitution fails, the compiler doesn't require it to compile; the overload is removed (almost as if it was never there for the given set of arguments). – Niall Jul 14 '16 at 11:56
  • @Niall how could this "hard error" then be converted to a "SFINAE compatible error" in order to be able to compile with GCC? Besides that, I am of course still interested which compiler (if any) is reacting correctly. – m.s. Jul 14 '16 at 11:57
  • @Niall Wait, wait, you say "since the substitution fails". What "substitution failure" are you talking about here? I'm seeing that the `int` version *is* being executed. That's what you're seeing too right? I've asked the question here if you would care to come clarify: http://stackoverflow.com/q/38373768/2642059 – Jonathan Mee Jul 14 '16 at 12:09
  • @m.s. I can't find a simple workaround for gcc on this; even moving it to the template arguments results in an error; http://coliru.stacked-crooked.com/a/1579af7b02a144e4. – Niall Jul 14 '16 at 12:13
  • @JonathanMee. There were two conversation going on in my head, it is if there had been a substitution failure, e.g. if its was `decltype(x.foo1(*this))`. In the sample code above, the `int` variant is chosen. – Niall Jul 14 '16 at 12:17
  • @m.s. I think clang would be correct, I've update the answer as well. – Niall Jul 14 '16 at 12:28
  • I fail to see how your demo solves the problem of my question – m.s. Jul 14 '16 at 12:49
  • I don't necessarily think it will, I'm sure your problem is larger than the MCVE examples you have posted; it is meant as further ideas and combining techniques. I have removed it. As for this question, I think clang's behaviour is most likely correct. – Niall Jul 14 '16 at 12:54
  • so is this a GCC bug I should report? – m.s. Jul 14 '16 at 13:00
  • 2
    @m.s. I would think so, yes, it should be reported. – Niall Jul 14 '16 at 13:03
  • 1
    @m.s. Would changing your declaration of of `foo` solve your issue? For example: `template decltype(declval().hello(), void()) foo(T&& x) { x.hello(); }` – Jonathan Mee Jul 14 '16 at 13:20
  • @JonathanMee No, unfortunately I cannot change `foo` – m.s. Jul 14 '16 at 13:27
  • @m.s. Eureka, I've finally found a reason for `result_of`. It *does* require the method to compile. So you can use it instead of `decltype` and C++ will correctly select your overload function: `template result_of_t call(T&& x, int)`: http://ideone.com/1Ew89R – Jonathan Mee Jul 14 '16 at 14:09
  • @Niall After reporting [this issue](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71879), I was told this is not a bug: "Taking the address of Foo::foo instantiates it, which results in an error outside the immediate context, so you get an error not a substitution failure. http://stackoverflow.com/a/15261234/981959" – m.s. Jul 16 '16 at 15:22
  • @m.s. I'm not too surprised, the address being taken is usually enough to require it to exist, but the `decltype` puts it in an unevaluted context, which is not really enough to require it to exist. If you filed the same issue with clang; that they compile the code when it shouldn't, you may receive the same response that it is not a bug. Interestingly enough, MSVC compiles the code as well. I mentioned this earlier, it is a corner case, I suspect the issue is that you don't want to depend on the outcome either way until (if at all) there is a definitive resolution. – Niall Jul 16 '16 at 16:44