4

I'm trying to recreate a simple example from this article on the common "overloaded lambda" trick to create an overload set that can be used with std::visit or other similar facilities. My simplified example is:

#include <iostream>
#include <vector>

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; }; // (1)
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;  // (2)

int main() {

    overloaded os(
        [](int i) { std::cout << "int: " << i << std::endl; }, 
        [](const char *str) { std::cout << "str: " << str << std::endl; }
    );

    os(1);
    os("Hello world!");

    return 0;
}

This does not compile.

<source>: In function 'int main()':
<source>:12:5: error: no matching function for call to 'overloaded<main()::<lambda(int)>, main()::<lambda(const char*)> >::overloaded(main()::<lambda(int)>, main()::<lambda(const char*)>)'
   12 |     );
      |     ^
<source>:4:30: note: candidate: 'constexpr overloaded<main()::<lambda(int)>, main()::<lambda(const char*)> >::overloaded(const overloaded<main()::<lambda(int)>, main()::<lambda(const char*)> >&)'
    4 | template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; }; // (1)
      |                              ^~~~~~~~~~
<source>:4:30: note:   candidate expects 1 argument, 2 provided
<source>:4:30: note: candidate: 'constexpr overloaded<main()::<lambda(int)>, main()::<lambda(const char*)> >::overloaded(overloaded<main()::<lambda(int)>, main()::<lambda(const char*)> >&&)'
<source>:4:30: note:   candidate expects 1 argument, 2 provided

If I change the initialization of overloaded os to use brace initialization, then it works. Can anyone explain the distinction here?

Fureeish
  • 12,533
  • 4
  • 32
  • 62
Jason R
  • 11,159
  • 6
  • 50
  • 81

1 Answers1

4

Here's a reduced example without any templates to deal with:

struct A { };
struct B { };

struct C : A, B { };

C x(A{}, B{}); // error
C y{A{}, B{}}; // ok

The issue is: C is an aggregate, so you can use aggregate initialization to initialize its components. This is why y works. But C is an aggregate, it doesn't have constructors, which is what the initialization of x is trying to do. There's no such matching constructor, hence it fails. Note that in C++20, x will also work, because we will be able to perform aggregate initialization with parentheses.

The way to get the declaration of x to compile is to add a constructor:

struct C : A, B {
    C(A a, B b) : A(a), B(b) { }
};

Or, for the original problem:

template<class... Ts>
struct overloaded : Ts... {
    overloaded(Ts... ts) : Ts(std::move(ts))... { } // <==
    using Ts::operator()...;
};

Or just stick with aggregate initialization, since that's more explicitly what we're doing here.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thanks for the explanation. I’m still getting used to C++17 facilities, and the syntax of the class template argument deduction guide gave me the mental picture that the guide would provide a function-like form that I could call with parentheses. Thinking more about it shows why that doesn’t make sense. – Jason R Jan 14 '20 at 01:24
  • @JasonR Yeah, for more fun in C++20, you both won't need the deduction guide anymore _and_ can use parentheses. Just to keep everyone on their toes. – Barry Jan 14 '20 at 01:24
  • @Barry mind pointing me to an explanation / sources on why we won't need the deduction guides in C++20? Will there be some default guides or will it be a thing for inheriting overloaded operators by default? There could be a number of things making it not needed anymore - just want to read the relevant information. Great answer and good catch on the wrongly closed question, though! – Fureeish Jan 14 '20 at 01:26
  • 1
    @Fureeish It'll just work out of the box for aggregates [P1816](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1816r0.pdf). – Barry Jan 14 '20 at 01:29