3

Consider the task of implementing an overload_two class so that the following code compiles:

int main()
{
    overload_two o{
        [](int) { return 1; },
        [](char){ return 2; }
    };

    static_assert(o(0) + o('a') == 3);
}

A first attempt would look something like this:

template <typename F, typename G>
struct overload_two : F, G
{
    overload_two(F f, G g) : F{f}, G{g} { }
};

Of course, this fails with the following error (gcc 11.0.1):

<source>:15:22: error: request for member 'operator()' is ambiguous
   15 |     static_assert(o(0) + o('a') == 3);
      |                      ^

<source>:12:9: note: candidates are: 'main()::<lambda(char)>'
   12 |         [](char){ return 2; }
      |         ^

<source>:11:9: note:                 'main()::<lambda(int)>'
   11 |         [](int) { return 1; },
      |         ^

The intended solution is to bring both F::operator() and G::operator() into scope with using declarations. However -- very surprisingly -- introducing a seemingly unrelated operator() also works:

template <typename F, typename G>
struct overload_two : F, G
{
    overload_two(F f, G g) : F{f}, G{g} { }
    auto operator()(F&) { }  // wtf?
};

live example on godbolt.org


What's more, clang 13.0.0 seems to work without neither the using declaration or the random operator(): live example on godbolt.org.


My questions are:

  1. Is gcc correct in rejecting the implementation of overload_two without any operator() or using declaration?

  2. Is gcc correct in accepting the implementation of overload_two with operator()(F&) and no using declaration?

  3. Is clang correct in accepting the implementation of overload_two without any operator() or using declaration?

1 Answer 1

7
  1. Yes. Name lookup for the name operator() is ambiguous because it found the name in both bases (see [class.member.lookup]/5.2). That's an error right there, don't pass GO, don't collect $200.
  2. Yes. The unrelated operator() makes name lookup succeed by hiding the base class members. Because your lambdas are captureless, they are implicitly convertible to function pointers, and these implicit conversions are inherited by the derived class. When your class is convertible to function pointers (or references), overload resolution will additionally generate surrogate call functions from these conversions. Those will be selected for the calls in the example.
  3. No. See #1.
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.