11

I'm investigating a C++11 idiom which might be called "overloaded lambda":

Overloading n functions with variadic template seemed very appealing to me but it turned out it didn't work with variable capture: any of [&] [=] [y] [&y] (and [this] etc if in a member function) lead to compilation failure: error: no match for call to '(overload<main(int, char**)::<lambda(int)>, main(int, char**)::<lambda(char*)> >) (char*&)' (with my local GCC 4.9.1 and ideone.com GCC 5.1)

On the other hand, the fixed 2-ary case didn't suffer that problem. (Try changing the first #if 0 to #if 1 on ideone.com)

Any ideas on what's happening here? Is this a compiler bug, or am I deviating from the C++11/14 spec?

http://ideone.com/dnPqBF

#include <iostream>
using namespace std;

#if 0
template <class F1, class F2>
struct overload : F1, F2 {
  overload(F1 f1, F2 f2) : F1(f1), F2(f2) { }

  using F1::operator();
  using F2::operator();
};

template <class F1, class F2>
auto make_overload(F1 f1, F2 f2) {
  return overload<F1, F2>(f1, f2);
}
#else
template <class... Fs>
struct overload;

template <class F0, class... Frest>
struct overload<F0, Frest...> : F0, overload<Frest...> {
  overload(F0 f0, Frest... rest) : F0(f0), overload<Frest...>(rest...) {}

  using F0::operator();
};

template <>
struct overload<> {
  overload() {}
};

template <class... Fs>
auto make_overload(Fs... fs) {
  return overload<Fs...>(fs...);
}
#endif

#if 0
#define CAP
#define PRINTY()
#else
#define CAP y
#define PRINTY() cout << "int y==" << y << endl
#endif

int main(int argc, char *argv[]) {
    int y = 123;

    auto f = make_overload(
        [CAP] (int x) { cout << "int x==" << x << endl; PRINTY(); },
        [CAP] (char *cp) { cout << "char *cp==" << cp << endl; PRINTY(); });
    f(argc);
    f(argv[0]);
}
Barry
  • 286,269
  • 29
  • 621
  • 977
nodakai
  • 7,773
  • 3
  • 30
  • 60
  • you should have `using overload::operator();` in the second implementation, [demo](http://coliru.stacked-crooked.com/a/c908c85a29e04004) – Piotr Skotnicki Sep 09 '15 at 09:22
  • and [this is why overloading with non-capturing lambdas works](http://coliru.stacked-crooked.com/a/dd9502d8e428c048) :-) – Piotr Skotnicki Sep 09 '15 at 09:32
  • @PiotrSkotnicki Interesting, but adding `using overload::operator();` didn't improve the situation. – nodakai Sep 09 '15 at 09:34
  • Ah, you're right, I didn't read carefully the error message from the online compiler... The missing `using overload::operator();` *was* the key. I also had to define the base case as unary: `template struct overload : F0 {overload(F0 f0) : F0(f0) {} using F0::operator();};` I'd accept your comments as a solution! – nodakai Sep 09 '15 at 10:00
  • "a non-capturing lambda defines a conversion operator" I didn't know that http://en.cppreference.com/w/cpp/language/lambda#ClosureType::operator_ret.28.2A.29.28params.29.28.29 – nodakai Sep 09 '15 at 10:03

2 Answers2

12

Overload resolution works only for functions that exist in a common scope. This means that the second implementation fails to find the second overload because you don't import function call operators from overload<Frest...> into overload<F0, Frest...>.

However, a non-capturing lambda type defines a conversion operator to a function pointer with the same signature as the lambda's function call operator. This conversion operator can be found by name lookup, and this is what gets invoked when you remove the capturing part.

The correct implementation, that works for both capturing and non-capturing lambdas, and that always calls operator() instead of a conversion operator, should look as follows:

template <class... Fs>
struct overload;

template <class F0, class... Frest>
struct overload<F0, Frest...> : F0, overload<Frest...>
{
    overload(F0 f0, Frest... rest) : F0(f0), overload<Frest...>(rest...) {}

    using F0::operator();
    using overload<Frest...>::operator();
};

template <class F0>
struct overload<F0> : F0
{
    overload(F0 f0) : F0(f0) {}

    using F0::operator();
};

template <class... Fs>
auto make_overload(Fs... fs)
{
    return overload<Fs...>(fs...);
}

DEMO

In , with class template argument deduction and pack expansion of using declarations in place, the above implementation can be simplified to:

template <typename... Ts> 
struct overload : Ts... { using Ts::operator()...; };

template <typename... Ts>
overload(Ts...) -> overload<Ts...>;

DEMO 2

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • 1
    Note that if you want many overloads (say, more than a few 100) in one overload set, you'll want to change the linear inheritance to a roughly balanced binary inheritance. And you'll want to rethink your design, because why are you overloading more than a few 100 lambdas at once? (Plus, the binary version is more code) – Yakk - Adam Nevraumont Sep 09 '15 at 12:03
2

Flat version of overload in C++11

Replying to the commenter on the accepted answer, here's a version which doesn't use recursive templates at all. This allows as many overloads as you need and only calls into 1 side template.

  namespace details {
    template<class F>
    struct ext_fncall : private F {
      ext_fncall(F v) :
        F(v) {}
      
      using F::operator();
    };
  }
  
  template<class... Fs>
  struct overload : public details::ext_fncall<Fs>... {
    overload(Fs... vs) :
      details::ext_fncall<Fs>(vs)... {}
  };
  
  template<class... Fs>
  overload<Fs...> make_overload(Fs... vs) {
    return overload<Fs...> {vs...};
  }

Explanation

The side template ext_fncall<class F> derives from a given functor and only exposes its operator(), which mimicks the given C++11 version.

The actual overload<class... Fs> derives from ext_fncall<Fs>..., which means that it only exposes operator() from the classes it derives (other members cannot be accessed due to ext_fncall<F>).

itzjackyscode
  • 970
  • 9
  • 27