oliversm
oliversm

Reputation: 1967

Type of a lambda function, using auto

I am trying to write a c++ lambda function, and don't like having to use auto as the type. Currently it looks like:

#include <iostream>

int main() {
//  Sends the address of an integer to a function which prints out the contents;
    auto print_int = [](int* a) {std::cout << *a << std::endl;};
    int a;
    a = 3;
    print_int(&a);
    return 0;
}

However, I would like to change the auto to something like std::function<void(int)> but am not sure how. The answers to

seems relevant, but I am not sure how to adapt it. Thanks.

Upvotes: 9

Views: 6243

Answers (3)

Guillaume Racicot
Guillaume Racicot

Reputation: 41780

Lambdas are meant to be used with either auto or as template parameter. You never know the type of a lambda and you can't type it. Each lambda has it's own unique type. Even if you knew the name of the type, their type names usually contains character prohibited in type names.

Why does lambda have their own type? because in reality, the compiler create a class defined a bit like that:

struct /* unnamed */ {

    // function body
    auto operator()(int* a) const {
        std::cout << *a << std::endl;
    }

} print_int; // <- instance name

This code is very close to an equivalent (I omitted conversion operator). As you can see, you already use auto, because lambdas are deducing the return type.

Some will say to use std::function<void(int*)>, but I disagree. std::function is a polymorphic wrapper around anything callable. Since lambdas are callable types, they fit into it. I other words, it work much like std::any but with a call operator. It will induce overhead in your application.

So what should you do?

use auto! auto isn't bad. In fact, it can even make your code faster and reduce unnecessary typing. If you feel uncomfortable with auto, well you shouldn't! auto is great, especially if you don't have the choice ;)

In fact, you could avoid using auto by using a template parameter:

template<typename F, typename Arg>
void parametric_print(F function, Arg&& arg) {
    function(std::forward<Arg>(arg));
}

Then use it like this:

int main() {
    int a = 3;
    parametric_print([](int* a) {std::cout << *a << std::endl;}, &a);
}

There you go, no auto! However, a template parameter is deduced with the same rule as auto. In fact, concept are accepted into the C++20 standard with terse function templates. You could write the same function template like this:

// C++20
void parametric_print(auto function, auto&& arg) {
    function(std::forward<decltype(arg)>(arg));
}

With concept in C++20, you can always give a constrain to a auto type, so you know what kind of thing that auto is:

std::invocable<int*> auto print_int = [](int* a) { std::cout << *a << std::endl; };

But it does not result in a different type, it just enforce some rules when deducing the type.

Finally, you can always give a name to an unnamed type using an alias:

auto print_int = [](int* a) { std::cout << *a << std::endl; };

using function_type = decltype(print_int);

// declare a second lambda of the same type
function_type also_print_int = print_int;

Upvotes: 11

eerorika
eerorika

Reputation: 238351

Here you go:

int a;
[](int* a) {std::cout << *a << std::endl;}(&a);

No auto used.


However, I would like to change the auto to something like std::function<void(int)> but am not sure how.

That is certainly possible. std::functional has a templated converting constructor. You simply pass the lambda to the constructor and that's all there is to it. Also, you'll need to fix the argument type since your lambda expects an int*, not an int (this is the kind of bug that auto fixes automatically).

std::function<void(int*)> print_int = [](int* a) {std::cout << *a << std::endl;};

Note however that there are two drawbacks to this.

  • std::function wrapper is an indirection layer that hides the actual type of the callable object. This wrapper layer adds runtime overhead and prevents some optimizations.
  • You have to manually update the type if you make changes to the return value, or arguments of the lambda.

Upvotes: 3

Jarod42
Jarod42

Reputation: 217275

The lambda has its own unnamed type.

Your may convert your lambda into std::function<void(int*)>
(or for captureless lambda to void (*)(int*)).

You may create your functor explicitly so you give it a name, something like:

class Print_int
{
public:
    void operator()(int* a) const {std::cout << *a << std::endl;}
};

and then

Print_int print_int;

Upvotes: 1

Related Questions