8

With gcc 4.9 -std=c++14, I tried making a vector of lambdas:

vector<function<void ()>> v = {[]{cout << "foo";}, []{cout << "bar";}};
for (auto&& a: v) a();

And it worked pretty well. Then I tried passing the initializer list of lambdas to the range-based for directly:

for (auto&& a: {[]{cout << "foo";}, []{cout << "bar";}}) a();

And I got:

error: unable to deduce 'std::initializer_list<auto>&&' from '{<lambda closure object>main()::<lambda()>{}, <lambda closure object>main()::<lambda()>{}}'

Judging by the appearance of the error message, I made a wild guess that it is probably because "lambda closure object"s are built-in language terms, and not direct equivalents of std::function (so no real types).

What is the deeper cause of this? Also, could this be implementation-related, or is such behavior dictated by the specification?

Thanasis Papoutsidakis
  • 1,661
  • 1
  • 12
  • 13
  • [A braced initializer has no type](https://www.youtube.com/watch?v=wQxj20X-tIU#t=1799). Which explains why you cant deduce it. – Borgleader Dec 21 '14 at 19:36
  • 2
    @Borgleader this is irrelevant here, *braced-init-list* in *range-based-for-loop* can be deduced as `initializer_list` of *something* – Piotr Skotnicki Dec 21 '14 at 19:42
  • @Borgleader Yes that also crossed my mind. It seems though from the error message that the compiler tries to create a typed one. That makes me unsure whether it's a fault of the initializer list not having a type or of the lambda closure not working well with it. – Thanasis Papoutsidakis Dec 21 '14 at 19:42

4 Answers4

6

Each lambda has its own unique type. So you may not build std::initializer_list from lambdas of different types.

According to the C++ Standard (5.1.2 Lambda expressions)

3 The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed nonunion class type — called the closure type — whose properties are described below.

Also

6 The closure type for a non-generic lambda-expression with no lambda-capture has a public non-virtual nonexplicit const conversion function to pointer to function with C++ language linkage (7.5) having the same parameter and return types as the closure type’s function call operator.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • So practically, a lambda closure that can be assigned to a variable of type `std::function` has a different type of another closure that can be assigned to the same variable? – Thanasis Papoutsidakis Dec 21 '14 at 19:46
  • @Jerry Coffin There is no implicit conversion function from one lambda to another lambda. – Vlad from Moscow Dec 21 '14 at 19:51
  • @Jerry Coffin Your point is wrong necause if you will write for ( int x : { 1, 'a' } ) the code will not be compiled. – Vlad from Moscow Dec 21 '14 at 19:57
  • @JerryCoffin But, maybe in that case the compiler infers the type of the initializer list from vector's initializer-list constructor, instead of the list's contents? (vector(initializer_list) ) – Thanasis Papoutsidakis Dec 21 '14 at 20:05
  • @Jerry Coffin There is a difference between the range based for statement and your example. – Vlad from Moscow Dec 21 '14 at 20:08
  • @ThanasisPapoutsidakis: you're at least getting closer. Unfortunately, Vlad's made it obvious that he's going to try to make excuses for his answer instead of fixing it, so I'm not going to keep beating my head against the wall trying to get him act reasonably. – Jerry Coffin Dec 21 '14 at 20:11
6

Each lamdba has its own type, so compiler cannot deduced the type of the initializer_list.

You have to tell which type you want:

  • For each lambda:

    • As your lambda doesn't capture variables, you may decay them to pointer to function with + as follow:

      for (auto&& a: {+[]{std::cout << "foo";}, +[]{std::cout << "bar";}}) a();
      
    • using function<void()>:

      for (auto&& a: {std::function<void()>([]{std::cout << "foo";}),
                      std::function<void()>([]{std::cout << "bar";})}) a();
      
  • For the initializer_list:

    for (auto&& a: std::initializer_list<std::function<void()>>{
                       []{std::cout << "foo";}, 
                       []{std::cout << "bar";}}) a();
    
Jarod42
  • 203,559
  • 14
  • 181
  • 302
1

One trick I learned here is to use a retrospective cast. So with such a tool at hand :

template<typename T>
struct memfun_type 
{
    using type = void;
};

template<typename Ret, typename Class, typename... Args>
struct memfun_type<Ret(Class::*)(Args...) const>
{
    using type = std::function<Ret(Args...)>;
};

template<typename F>
typename memfun_type<decltype(&F::operator())>::type
FFL(F const &func) 
{ // Function from lambda !
    return func;
}

you could write something like this

vector<function<void()>> v = { FFL([]{cout << "foo"; }), FFL([]{cout << "bar"; }) };

for (auto&& a : v) a();
Community
  • 1
  • 1
Lorah Attkins
  • 5,331
  • 3
  • 29
  • 63
  • It's worth noting that this approach fails with the use of C++14 generic lambdas. – ildjarn Dec 22 '14 at 06:02
  • @ildjarn Why is that ? ( I mean apart from the comment in the provided link? ) – Lorah Attkins Dec 22 '14 at 07:55
  • 1
    I hadn't seen it until just now, but yes, I'm referring to the same thing as R. Martinho Fernandes - it's not possible to take the address of a function template. – ildjarn Dec 22 '14 at 10:15
  • @ildjarn Thnx for the clarification! This means it would still work for plain old lambdas right? – Lorah Attkins Dec 22 '14 at 12:59
  • 1
    For C++11 lambdas, yes, but once C++14 support is widespread, I think generic lambdas will themselves be considered plain old lambdas. ;-] – ildjarn Dec 22 '14 at 18:17
0

Each lambda is an unrelated type. As it happens, they can all be converted to std::function<void()>, but that is because std::function will claim to convert anything, and it will work when they are invokable with void() signature and copyable and destructible.

In the vector case, there is an std::initializer_list<std::function<void()>> constructor that is considered from its list of constructors. This matches, is attempted, and compiles.

Without that argument (the list argument to the vector ctor) to match, the {} syntax instead looks inside at its contents for a common type. There is no common type, so it fails.

The language does not search every type and template to find a possible common type between the two (unrelated) lambdas. It will not read your mind.

You can do:

 using nullary = std::function<void()>;
 template<class T>
 using il=std::initializer_list<T>;
 for(auto f:il<nullary>{[]{ std::cout<<"hello";},[]{std::cout<<" world\n";}}){
   f();
 }
AndyG
  • 39,700
  • 8
  • 109
  • 143
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524