msvc visual c++ incorrect formation of bound member function expression from static member function

143 views Asked by At

Consider the following code:

#include <type_traits>

struct A {
    template<typename T, std::enable_if_t<sizeof(T)<=sizeof(int), int> = 0>
    static void fun() {}
    template<typename T, std::enable_if_t<(sizeof(T)>sizeof(int)), int> = 0>
    static void fun() {}
    void test() {
        using fun_t = void(*)();
        fun_t ff[] = {
            fun<int>,
            &fun<int>,
            A::fun<int>,
            &A::fun<int>,
            this->fun<int>,
            &this->fun<int>, //error C2276: '&': illegal operation on bound member function expression
        };
    }
};

msvc 2015update3 and 2017rc generate error C2276, which makes no sense. gcc, clang and intel compiler are fine.

The work-around is simple: use any of the other expressions above which should all be equivalent.

However, the wording of the error message is disturbing, and one has to wonder, if a bound member function expression is incorrectly formed for any of the other alternatives.

Any insights into this matter?

1

There are 1 answers

0
Oktalist On

This is a nice, dark corner of the standard for the language lawyers to feast upon.

[expr.ref] ¶4.3

If E2 is a (possibly overloaded) member function, function overload resolution is used to determine whether E1.E2 refers to a static or a non-static member function.

  • If it refers to a static member function and the type of E2 is "function of parameter-type-list returning T", then E1.E2 is an lvalue; the expression designates the static member function. The type of E1.E2 is the same type as that of E2, namely "function of parameter-type-list returning T".

  • Otherwise, if E1.E2 refers to a non-static member function and the type of E2 is "function of parameter-type-list cv ref-qualifieropt returning T", then E1.E2 is a prvalue. The expression designates a non-static member function. The expression can be used only as the left-hand operand of a member function call. The type of E1.E2 is "function of parameter-type-list cv returning T".

It goes without saying that only pointers to static member functions can be formed by the member access expression (i.e. dot or arrow operator). What is to be noted, though, is that overload resolution determines the "staticness" of a member function. So the question is, how can overload resolution act without an argument list? Compilers seem to differ on their interpretation.

Some compilers may recognize that because the expression is being used to form a pointer to a function, the function must be a static member function or else the program is ill formed, and so exclude non-static member functions from the set of candidate functions.

Other compilers may recognize that because all of the overloads are static, overload resolution can not possibly select a non-static member function, and so skip the step altogether.

Yet other compilers may give up entirely, faced with the instruction to apply overload resolution without any argument list.

This is a related example:

struct A {
    static void foo(int) {}
    static void bar(int) {}
    static void bar(double) {}
};

void test() {
    A a;
    void(*f)(int) =  a.foo; // gcc:OK, msvc:OK,    clang:OK
    void(*g)(int) =  a.bar; // gcc:OK, msvc:OK,    clang:ERROR
    void(*h)(int) = &a.bar; // gcc:OK, msvc:ERROR, clang:ERROR
}

Here Clang is unable to form a pointer to the int overload of bar using the member access expression, with or without the & address-of operator. MSVC is inconsistent in that it succeeds without the & but not with it. But for the aforementioned reasons it is difficult to say which is conformant. GCC is quite happy in all cases to let the member access expression designate an unresolved overload that is then resolved due to the target type of the initialization ([over.over] ¶1).

It gets even more interesting when SFINAE gets involved:

[temp.over] ¶1

...If, for a given function template, argument deduction fails or the synthesized function template specialization would be ill-formed, no such function is added to the set of candidate functions for that template...

Thus, some compilers may exclude SFINAE failures from the set of candidate functions for overload resolution for the purpose of determining the "staticness" of a member function. But again, it is hard to say what is conformant here. This may explain why Clang accepts the example in the original question, but not my bar example. I get the feeling implementors are just trying their best to fill in the blanks here.