54

Since C++14 we can use generic lambdas:

auto generic_lambda = [] (auto param) {};

This basically means that its call operator is templated based on the parameters marked as auto.

The question is how to create a lambda that can accept a variadic number of parameters similarly to how a variadic function template would work ? If this is not possible what is the closest thing that could be used the same way ? How would you store it ? Is it possible in a std::function ?

Drax
  • 12,682
  • 7
  • 45
  • 85
  • 29
    `[](auto... params){}` ... ? – Xeo Sep 17 '14 at 08:38
  • @Xeo Nice ! How would you use it ? Can you store it in a std::function ? – Drax Sep 17 '14 at 08:40
  • 1
    No, since `std::function`'s call signature isn't polymorphic by necessity. – Xeo Sep 17 '14 at 08:54
  • 6
    example of usage : [weird lambda](http://coliru.stacked-crooked.com/a/894842343027d6ba) – uchar Sep 17 '14 at 09:05
  • why a bounty? @Xeo already answered your question, and surely you can store it in a `std::function`, but it won't be polymorphic. – Jamboree Sep 19 '14 at 08:56
  • @Jamboree The reason for the bounty is within the bounty grey zone :) – Drax Sep 19 '14 at 10:23
  • I think it is not possible to apply type erasure to an unrestricted set of function overloads: It typically works by pre-instantiating a finite amount of use cases (when the type is available), storing these cases hidden behind some abstraction (e.g. function pointers) and using this abstraction later to "restore" all required types. As you cannot create nor store the "infinite" set required for unrestricted polymorphic lambdas, I guess it's not possible. – dyp Sep 20 '14 at 09:55
  • @Xeo in which compiler it works? – GingerPlusPlus Sep 20 '14 at 09:56
  • 2
    @GingerPlusPlus [GCC 4.9 and Clang 3.5](http://coliru.stacked-crooked.com/a/cb64e3e8e230dd26) – Xeo Sep 20 '14 at 12:30

3 Answers3

57

I am not sure what your intention is but instead of storing it in a std::function you can use the lambda itself to capture the params. This is an example discussed on the boost mailing list. It is used in the boost::hana implementation

auto list = [](auto ...xs) {
    return [=](auto access) { return access(xs...); };
};

auto head = [](auto xs) {
    return xs([](auto first, auto ...rest) { return first; });
};

auto tail = [](auto xs) {
    return xs([](auto first, auto ...rest) { return list(rest...); });
};

auto length = [](auto xs) {
    return xs([](auto ...z) { return sizeof...(z); });
};

// etc...
// then use it like

auto three = length(list(1, '2', "3")); 
mkaes
  • 13,781
  • 10
  • 52
  • 72
51

Syntax

How do you create a variadic generic lambda ?

You can create a variadic generic lambda with the following syntax:

auto variadic_generic_lambda = [] (auto... param) {};

Basically you just add ... between auto (possibly ref qualified) and your parameter pack name.

So typically using universal references would give:

auto variadic_generic_lambda = [] (auto&&... param) {};

Usage

How do you use the parameters ?

You should consider the variadic generic parameter as having a template parameter pack type, because it is the case. This more or less implies that most if not all usage of those parameters will require templates one way or the other.

Here is a typical example:

#include <iostream>

void print(void)
{
}

template <typename First, typename ...Rest>
void print(const First& first, Rest&&... Args)
{
  std::cout << first << std::endl;
  print(Args...);
}

int     main(void)
{
  auto variadic_generic_lambda = [] (auto... param)
    {
      print(param...);
    };

  variadic_generic_lambda(42, "lol", 4.3);
}

Storage

How do you store a variadic generic lambda ?

You can either use auto to store a lambda in a variable of its own type, or you can store it in a std::function but you will only be able to call it with the fixed signature you gave to that std::function :

auto variadic_generic_lambda = [] (auto... param) {};

std::function<void(int, int)> func = variadic_generic_lambda;

func(42, 42); // Compiles

func("lol"); // Doesn't compile

What about collections of variadic generic lambdas ?

Since every lambda has a different type you cannot store their direct type in the usual homogeneous containers of the STL. The way it is done with non generic lambdas is to store them in a corresponding std::function which will have a fixed signature call and that won't restrain anything since your lambda is not generic in the first place and can only be invoked that way:

auto non_generic_lambda_1 = [] (int, char) {};
auto non_generic_lambda_2 = [] (int, char) {};

std::vector<std::function<void(int, char)>> vec;

vec.push_back(non_generic_lambda_1);
vec.push_back(non_generic_lambda_2);

As explained in the first part of this storage section if you can restrain yourself to a given fixed call signature then you can do the same with variadic generic lambdas.

If you can't you will need some form of heterogenous container like:

  • std::vector<boost::variant>
  • std::vector<boost::any>
  • boost::fusion::vector

See this question for an example of heterogenous container.

What else ?

For more general informations on lambdas and for details on the members generated and how to use the parameters within the lambda see:

Community
  • 1
  • 1
Drax
  • 12,682
  • 7
  • 45
  • 85
  • @Mikhail `Basically you just add ... between auto (possibly ref qualified) and your parameter pack name.` So typically in a case with a parameter pack of universal references you would have `[](auto&&... params) {}`. Not sure if it's worth adding this specific example or not to the post. – Drax Jan 28 '16 at 15:16
  • 2
    That's what I was looking for when found your post. Perhaps, I am not the only one. Thank you. – Mikhail Jan 28 '16 at 16:26
  • could you please show an example where the arguments are used inside the lambda -- to many examples of what you are writing here, but no one gives a complete example – serup Jan 26 '17 at 14:35
  • @serup You use the paramaters the same way you would use a template parameter pack, other questions like this one (http://stackoverflow.com/q/17339789/1147772) cover this, i'll add the link to the `what else` section – Drax Jan 26 '17 at 15:30
  • @Drax, this example does not show lambda functions only template functions – serup Jan 31 '17 at 07:54
  • @serup Fair enough, i was about to answer in a comment but while writing it i considered it would deserve its own section in this answer, the example is inspired from http://stackoverflow.com/q/7124969/1147772 – Drax Feb 01 '17 at 10:58
7

Consider this

#include <iostream>

    namespace {

        auto out_ = [] ( const auto & val_) 
        {
            std::cout << val_;
            return out_ ;
        };

        auto print = [](auto first_param, auto... params)
        {
            out_(first_param);

            // if there are  more params
            if constexpr (sizeof...(params) > 0) {
                // recurse
                print(params...);
            }
                return print;
        };
    }

int main()
{
    print("Hello ")("from ")("GCC ")(__VERSION__)(" !"); 
}

(wandbox here) This "print" lambda is:

  • Variadic
  • Recursive
  • Generic
  • Fast

And no templates in sight. (just underneath :) ) No C++ code that looks like radio noise. Simple, clean and most importantly:

  • Easy to maintain

No wonder "it feels like a new language".

  • In order not to muddle the answer I deliberately just said "Fast". It is actually very fast. Because it is almost a compile time code. Hence the if-constexpr optimization is possible. –  Feb 28 '18 at 10:04
  • this is also why I have refrained from the proper signature auto print = [](auto && first_param, auto && ... params) it is in the wand box example provided in the answer –  Feb 28 '18 at 10:08
  • @Nikos, I don't understand why you think that it's not recursive? The "print" lambda calls itself. This is compile-time recursive, and any good compiler would convert it to non-recursive for run-time. Is that what you mean? – Elliott Feb 12 '20 at 04:14
  • Hi @Elliot , who is "Nikos" ? – Chef Gladiator Aug 12 '20 at 21:09
  • @ChefGladiator I honestly can't remember this conversation, but I'm guessing that some "Nikos" deleted their comment... – Elliott Aug 13 '20 at 07:06
  • 1
    HI @Elliott, guess what? That user and code above actually were me. But that code does not work anymore for both "updated" g++ and clang. Precisely it works now with "updated" MSVC only. What should I do? – Chef Gladiator Aug 14 '20 at 06:49
  • @ChefGladiator, that's strange - why did you ask who "Nikos" was if you already knew? Also, what do you mean by "updated" g++/clang? The above code is fine, but because it uses `constexpr` it requires C++17. – Elliott Aug 14 '20 at 08:02
  • 1
    @Elliott what makes you think I knew who the Nikos was? Irrelevant now. Back to business -- wand box share above prints "Hello from GCC 8.0.1 20180226 (experimental) !" ... Using gcc 10.x or clang 10.x , that does not compile any more. Using CL.exe 19.27.x this compiles. gcc 9.3.x compiles but gcc 10.0.x does not ... – Chef Gladiator Aug 14 '20 at 15:08