Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Selection of inherited operator contrary to `using` clause in C++

In the following example struct S inherits from two functional objects A and B each with its own operator (), and then declares using A::operator() to take the operator from A:

using A = decltype([](int){ return 1; });
using B = decltype([](){ return 2; });

struct S : A, B {
    using A::operator();
};

int main() {
    S s;
    static_assert( s() == 2 ); // passes in GCC and Clang, but why?
}

As I expected, this code is rejected by MSVC with the error:

error C2064: term does not evaluate to a function taking 0 arguments

because A::operator(int) indeed takes 1 argument and B::operator() shall not be considered.

However both GCC and Clang accept the code and call B::operator() in static_assert. Demo: https://gcc.godbolt.org/z/x6x3aWzoq

Which compiler is right here?

like image 787
Fedor Avatar asked Oct 17 '21 05:10

Fedor


1 Answers

GCC (and Clang) are correct in this case.

A captureless nongeneric lambda has a conversion function to function pointer ([expr.prim.lambda.closure]/8), which is inherited by S (and doesn't conflict since the conversion functions from A and B convert to different types). So during overload resolution for a function call expression like s(), surrogate call functions are introduced for each conversion function ([over.call.object]/2). The one introduced from B's conversion function is the only viable candidate, so it is selected by overload resolution, and the call is performed by converting s to a function pointer first and calling that.

You can see this by actually compiling a s(); call with optimization disabled; a the call to the conversion function will be emitted.


IIRC MSVC's lambdas have multiple conversion functions to function pointers for all the different calling conventions, which makes the overload resolution ambiguous.

like image 96
T.C. Avatar answered Sep 28 '22 10:09

T.C.