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

1

10

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?

Psia answered 17/10, 2021 at 5:47 Comment(4)
I have another question: why is static_assert allowed? Is s() constexpr? Why?Asafoetida
@Eugene. Lambda's operator() is implicitly constexpr since C++17.Cathryncathy
Clang even compiles without using A::operator().Cathryncathy
@康桓瑋 looks like bugs.llvm.org/show_bug.cgi?id=22039Herwick
P
4

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

Pazice answered 24/10, 2021 at 3:55 Comment(2)
Yep, checks out. Cannot delete my answer so will amend mine and let the OP know. Have a +1 anyway. ThanksKobi
Thanks for the explanation! This is an equivalent code to the question, but without lambdas: gcc.godbolt.org/z/38T35YfvsPsia

© 2022 - 2024 — McMap. All rights reserved.