69

Playing around with Lambdas I found an interesting behaviour that I do not fully understand.

Supose I have a struct Overload that derives from 2 template parameters, and has a using F1::operator(); clause.

Now if I derive from two functors I can only access the operator() of F1 (as I would expect)

If I derive from two Lambda Functions this is no longer true: I can access the operator() from F2 too.

#include <iostream>

// I compiled with g++ (GCC) 4.7.2 20121109 (Red Hat 4.7.2-8)
//
// g++ -Wall -std=c++11 -g main.cc
// g++ -Wall -std=c++11 -DFUNCTOR -g main.cc
// 
// or clang clang version 3.3 (tags/RELEASE_33/rc2)
// 
// clang++ -Wall -std=c++11 -g main.cc
// clang++ -Wall -std=c++11 -DFUNCTOR -g main.cc
// 
// on a Linux localhost.localdomain 3.9.6-200.fc18.i686 #1 SMP Thu Jun 13 
// 19:29:40 UTC 2013 i686 i686 i386 GNU/Linux box


struct Functor1
{
    void operator()() { std::cout << "Functor1::operator()()\n"; }
};

struct Functor2
{
    void operator()(int) { std::cout << "Functor2::operator()(int)\n"; }
};

template <typename F1, typename F2>
struct Overload : public F1, public F2
{
    Overload()
        : F1()
        , F2() {}

    Overload(F1 x1, F2 x2)
        : F1(x1)
        , F2(x2) {}

    using F1::operator(); 
};

template <typename F1, typename F2>
auto get(F1 x1, F2 x2) -> Overload<F1, F2>
{
   return Overload<F1, F2>(x1, x2);
}


int main(int argc, char *argv[])
{
    auto f = get(Functor1(), Functor2());

    f();
#ifdef FUNCTOR
    f(2); // this one doesn't work IMHO correctly
#endif

    auto f1 = get(
                  []() { std::cout << "lambda1::operator()()\n"; },
                  [](int) { std::cout << "lambda2::operator()(int)\n"; }
                  );
    f1();
    f1(2); // this one works but I don't know why


  return 0;
}

The standard states that:

The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed non- union class type

So every Lambda's types should be unique.

I cannot explain why this is so: can anyone shed some light on this please?

Mat
  • 202,337
  • 40
  • 393
  • 406
gu1d0
  • 699
  • 5
  • 9

2 Answers2

35

In addition to operator(), a the class defined by a lambda can (under the right circumstances) provide a conversion to a pointer to function. The circumstance (or at least the primary one) is that the lambda can't capture anything.

If you add a capture:

auto f1 = get(
              []() { std::cout << "lambda1::operator()()\n"; },
              [i](int) { std::cout << "lambda2::operator()(int)\n"; }
              );
f1();
f1(2);

...the conversion to pointer to function is no longer provided, so trying to compile the code above gives the error you probably expected all along:

trash9.cpp: In function 'int main(int, char**)':
trash9.cpp:49:9: error: no match for call to '(Overload<main(int, char**)::<lambda()>, main(int, char**)::<lambda(int)> >) (int)'
trash9.cpp:14:8: note: candidate is:
trash9.cpp:45:23: note: main(int, char**)::<lambda()>
trash9.cpp:45:23: note:   candidate expects 0 arguments, 1 provided
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • Wait. Are you saying you can inherit from function pointer types? – zneak Aug 25 '13 at 19:02
  • @zneak: No, not at all. You can inherit from a lambda because it defines a class, not a function. – Jerry Coffin Aug 25 '13 at 19:03
  • `struct Overload : public F1, public F2`, with F1 and F2 deduced as function pointer types, according to your hypothesis, if I read it right. – zneak Aug 25 '13 at 19:04
  • 6
    @zneak: no - the lambdas have `operator (function-pointer-type)()` members in this case. These members are inherited and "visible" in the derived class. So `f1(2)` is "two-step" conversion to function pointer + function call. (If I got that right.) – Mat Aug 25 '13 at 19:07
  • Oh, I see. If I were you, I would add that this works possibly because the conversion operators are inherited to your answer. – zneak Aug 25 '13 at 19:09
  • 7
    @jrok: This answer is completely correct. The two calls to `f1` in the OP's example are doing different things. One is calling the lambda's `operator()` (pulled in by the `using` clause). The other is calling a function pointer (the result of an implicit conversion). Jerry is pointing out that lambdas provide more than just `operator()` (like OP's `Functor1` and `Functor2`); they also provide an implicit conversion to a function pointer when they are stateless. – Nemo Aug 25 '13 at 19:13
  • @Nemo I know that. What's beyond me is how is the compiler able to deduce that the conversion is neccesary and possible here. – jrok Aug 25 '13 at 19:15
  • I would like to [submit this as further evidence that you're right](http://ideone.com/XN17Zd): F1 and F2 both declare a different function-pointer cast operator. – zneak Aug 25 '13 at 19:17
  • @jrok: that would be in §13.3.1.1.2/2 (and following) "surrogate function calls" – Mat Aug 25 '13 at 19:18
  • @Mat Thank you very much! TIL surrogate function calls. – jrok Aug 25 '13 at 19:20
  • @Mat Nitpick: it's really "surrogate call functions". But thanks =) – gx_ Aug 25 '13 at 19:43
  • A weird behavior in gcc4.8.1 http://ideone.com/jEG3Dr is that it's printing `lambda2::operator()(int)` while `f1(2)` is supposed to call `operator ret(*)` – a.lasram Aug 25 '13 at 22:24
  • You are perfectly right I overlook the paragraph about conversion. You can get the same result in a home made functor like the following one `struct Funct0 { private: static int f() { return 22; } public: int operator()() { return f(); } typedef int (*ptr)(); operator ptr() const { return ptr(&f); } };` – gu1d0 Aug 26 '13 at 08:49
  • @a.lasram: Why is that weird? And why is it supposed to call `operator ret(*)`? – Nawaz Aug 26 '13 at 16:12
  • @Nawaz Nothing's weird, it's my comment that is weird. `operator ret(*)` will be called and the returned function will print the same message as the lambda when called. My previous comment must be ignored – a.lasram Aug 26 '13 at 21:45
16

A lambda generates a functor class.

Indeed, you can derive from lambdas and have polymorphic lambdas!

#include <string>
#include <iostream>

int main()
{
    auto overload = make_overload(
        [](int i)          { return '[' + std::to_string(i) + ']'; },
        [](std::string s)  { return '[' + s + ']'; },
        []                 { return "[void]"; }
        );

    std::cout << overload(42)              << "\n";
    std::cout << overload("yay for c++11") << "\n";
    std::cout << overload()                << "\n";
}

Prints

[42]
[yay for c++11]
[void]

How?

template <typename... Fs>
   Overload<Fs...> make_overload(Fs&&... fs)
{
    return { std::forward<Fs>(fs)... };
}

Of course... this still hides the magic. It is the Overload class that 'magically' derives from all the lambdas and exposes the corresponding operator():

#include <functional>

template <typename... Fs> struct Overload;

template <typename F> struct Overload<F> {
    Overload(F&& f) : _f(std::forward<F>(f)) { }

    template <typename... Args>
    auto operator()(Args&&... args) const 
    -> decltype(std::declval<F>()(std::forward<Args>(args)...)) {
        return _f(std::forward<Args>(args)...);
    }

  private:
    F _f;
};

template <typename F, typename... Fs>
   struct Overload<F, Fs...> : Overload<F>, Overload<Fs...>
{
    using Overload<F>::operator();
    using Overload<Fs...>::operator();

    Overload(F&& f, Fs&&... fs) :  
        Overload<F>(std::forward<F>(f)),
        Overload<Fs...>(std::forward<Fs>(fs)...)
    {
    }
};

template <typename... Fs>
   Overload<Fs...> make_overload(Fs&&... fs)
{
    return { std::forward<Fs>(fs)... };
}

See it Live on Coliru

sehe
  • 374,641
  • 47
  • 450
  • 633
  • 9
    Although this is interesting, I do not see how it answers the question. – Nemo Aug 25 '13 at 19:20
  • @Nemo ah. Apparently I misread the question slightly. Nevertheless, the code in my answer seems to contradict the claim by the OP that "If I derive from two Lambda Functions this is no longer true: I can access the operator() from F2 too". If you lose the `using base::operator()` line the code won't work anymore. I do suppose this is because I opted for chained inheritance, rather than multiple inheritance – sehe Aug 25 '13 at 20:12