Why Can't I Create a Vector of Lambdas (Of the Same Type) in C++11

Why can't I create a vector of lambdas (of the same type) in C++11?

Every lambda has a different type—even if they have the same signature. You must use a run-time encapsulating container such as std::function if you want to do something like that.

e.g.:

std::vector<std::function<int()>> functors;
functors.push_back([&] { return 100; });
functors.push_back([&] { return 10; });

How to declare a vector of functions (lambdas)

Use std::function with the corresponding type:

std::vector<std::function<void(void)>> vec;
vec.push_back([]()->void{});

In your case it would be std::function<void(std::string)>.

The exact type of a lambda is meaningless per standard ([expr.prim.lambda]/3):

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

Why can't we directly use lambdas without mentioning its type in some STL containers?

You have mixed up the function template and the class template. For both cases, for any code to appear, it must be instantiated. Let's look at each case:

The std::sort is a function template which accepts the binary-predicate (i.e. callable compare function/ function objects) as its third parameter.

template< class RandomIt, class Compare >
constexpr void sort( RandomIt first, RandomIt last, Compare comp ); (since C++20)
^^^^^^^^^^^^

Here the compiler will try to determine template argument types (i.e. RandomIt and Compare) from the passed function parameters, so-called implicit instantiation will happen automatically if you do not explicitly mention them.

That means, one could also call the std::sort with template parameters (explicit instantiation):

const auto compare = [](const int& num1, const int& num2) { ...
std::sort<std::vector<int>::iterator, decltype(compare)>(a.begin(), a.end(), compare);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Whereas, the std::set is a class template. As long as you do not specify the template parameters here, you will not get a concrete type.

A class template by itself is not a type, or an object, or any other entity. No code is generated from a source file that contains only template definitions. In order for any code to appear, a template must be instantiated: the template arguments must be provided so that the compiler can generate an actual class (or function, from a function template).

template<
class Key,
class Compare = std::less<Key>, // optional
class Allocator = std::allocator<Key>
> class set;

Here, the Compare is optional but part of the std::set's class template type, which can be replaced with the user defined compare function/ callable. Without providing the type of callable, the compiler can not instantiate the version of std::set you want.


Further Reads:

  • The std::pair has compare operations defined. Hence, the PredicateLambda is not required in your example code. I hope that it is for demonstration purpose.

  • Since c++17 we have deduction guide, for a template class, by which one can simply write:

    #include <set>
    #include <tuple>
    #include <string>
    using namespace std::string_literals;

    std::set st{ std::pair{1, "string"s} }; // st uses the

    One can also provide own deduction guides

  • Since c++20 lambda expression is default constructible. Hence,
    you may not need to pass the PredicateLambda as argument to the st, in the newer
    compilers.

    std::set<std::pair<int, std::string>, decltype(PredicateLambda)> st;

Return the same type as a lambda expression passed as argument

You may use deduced return type:

template <typename F>
auto foo(F f) -> decltype(f())
{
return f();
}

And if F takes some arguments :

template <typename F, typename ... Args>
auto foo(F f, Args&&...args) -> decltype(f(std::forward<Args>(args)...))
{
return f(std::forward<Args>(args)...);
}

If lambdas don't have a specified type, how does std::function accept a lambda?

The type is there. It’s just that you don’t know in advance what it is. Lambdas have type - just the standard says nothing about what that type is; it only gives the contracts that type has to fulfill. It’s up to the compiler implementers to decide what that type really is. And they don’t have to tell you. It’s not useful to know.

So you can deal with it just like you would deal with storage of any “generic” type. Namely: provide suitably aligned storage, then use placement new to copy-construct or move-construct the object in that storage. None of it is specific to std::function. If your job was to write a class that can store an arbitrary type, you’d do just that. And it’s what std::function has to do as well.

std::function implementers usually employ the small-object optimization. The class leaves some unused room in its body. If the object to be stored is of an alignment for which the empty room is suitable, and if it will fit in that unused room, then the storage will come from within the std::function object itself. Otherwise, it’ll have to dynamically allocate the memory for it. That means that e.g. capture of intrinsic vector types (AVX, Neon, etc) - if such is possible - will usually make a lambda unfit for small object optimization storage within std::function.

I'm making no claims as to whether or if the capture of intrinsic vector types is allowed, fruitful, sensible, or even possible. It's sometimes a minefield of corner cases and subtle platform bugs. I don't suggest anyone go and do it without full understanding of what's going on, and the ability to audit the resulting assembly as/when needed (implication: under pressure, typically at 4AM on the demo day, with the suits about to wake up soon - and already irked that they have to interrupt their golf play so early in the day just to watch the presenter sweat).

Lambda closure cannot be converted to std::function

Because you forgot a return:

f([](int x, int y = 10) { return g(x,y); }, 20);

Without return, your lambda doesn’t return a value, and C++ infers a void return type.



Related Topics



Leave a reply



Submit