Using a range of lambdas initializers in a range-based loop

With gcc 4.9 -std = C ++ 14, I tried to make the lambdas vector:

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

And it worked very well. Then I tried passing the list of lambdas initializers to a range based 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 suggested that this is probably because the β€œlambda closure object” is the built-in language terms and not the direct equivalents of std :: function (so there are no real types).

What is the deeper reason for this? Also, can it be implementation related, or is this behavior dictated by the specification?

+6
source share
4 answers

Each lambda has its own unique type. Thus, you cannot create std :: initializer_list from lambdas of different types.

According to C ++ standard (5.1.2 Lambda expressions)

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

Besides

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

+6
source

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

You must specify what type you want:

  • For each lambda:

    • Since your lambda does not capture variables, you can decompose them into a function pointer using + as follows:

       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 file:

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

Each lambda is an unrelated type. Be that as it may, all of them can be converted to std::function<void()> , but this is because std::function will require to convert something, and it will work when they are invokable with void() signature and copyable and destructible.

In the case of vector there is a constructor std::initializer_list<std::function<void()>> , which is considered from its list of constructors. This is a coincidence, attempt and compilation.

Without this argument (list argument to ctor vector), to match, the syntax {} instead scans its contents for a generic type. There is no ordinary type, so it fails.

The language does not search every type and pattern to find a possible common type between two (unrelated) lambdas. He 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(); } 
0
source

One trick I learned here is to use retrospective cast. So, with the help of such a tool:

 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(); 
0
source

Source: https://habr.com/ru/post/980009/


All Articles