Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++14 Lambda - Conditionally Capture by Reference or Value

Tags:

c++

lambda

c++14

Is it possible to conditionally choose the capture method of a lambda based on compile time information? For example...

auto monad = [](auto && captive) {
    return [(?)captive = std::forward<decltype(captive)>(captive)](auto && a) {
        return 1;
    };
};

I want capture by reference if decltype(captive) is a std::reference_wrapper, and everything else captured by value.

like image 459
pat Avatar asked Nov 26 '14 15:11

pat


People also ask

What does [=] mean in lambda function?

The [=] you're referring to is part of the capture list for the lambda expression. This tells C++ that the code inside the lambda expression is initialized so that the lambda gets a copy of all the local variables it uses when it's created.

What is lambda capture in C++?

A capture clause of lambda definition is used to specify which variables are captured and whether they are captured by reference or by value. An empty capture closure [ ], indicates that no variables are used by lambda which means it can only access variables that are local to it.

What is a mutable lambda?

The mutable keyword is used so that the body of the lambda expression can modify its copies of the external variables x and y , which the lambda expression captures by value. Because the lambda expression captures the original variables x and y by value, their values remain 1 after the lambda executes. C++ Copy.


2 Answers

Lambda capture type cannot be controlled by template-dependent names.

However, you could achieve the desired effect by delegating creating the inner lambda to an overloaded function:

template<class T>
auto make_monad(T&& arg) {
    return [captive = std::forward<T>(arg)](auto&& a) {
        std::cout << __PRETTY_FUNCTION__ << " " << a << '\n';
        return 1;
    };
}

template<class T>
auto make_monad(std::reference_wrapper<T> arg) {
    return [&captive = static_cast<T&>(arg)](auto&& a) {
        std::cout << __PRETTY_FUNCTION__ << " " << a << '\n';
        return 1;
    };
}

int main() {
    auto monad = [](auto&& captive) {
        return make_monad(std::forward<decltype(captive)>(captive));
    };

    int n = 1;
    monad(1)(1);
    monad(n)(2);
    monad(std::ref(n))(3);
}

Outputs:

make_monad(T&&)::<lambda(auto:1&&)> [with auto:1 = int; T = int] 1
make_monad(T&&)::<lambda(auto:1&&)> [with auto:1 = int; T = int&] 2
make_monad(std::reference_wrapper<_Tp>)::<lambda(auto:2&&)> [with auto:2 = int; T = int] 3

I don't want to capture reference_wrapper by reference, I want to capture the reference it holds by reference. Reference wrapper does it's best to be a like a reference, but since the call operator (aka, "." operator) cannot be overloaded, it fails pretty miserably at the end of the day.

In this case you do not need to change the capture type for std::reference_wrapper<T>. Instead, you may like to capture it by value like any other type of argument and at the usage site unwrap the argument first:

template<class T> T& unwrap(T& t) { return t; }
template<class T> T& unwrap(std::reference_wrapper<T> t) { return t; }

auto monad = [](auto && captive) {
    return [captive](auto && a) {            // <--- Capture by value.
        auto& captive_ref = unwrap(captive); // <--- Unwrap before usage.
        return 1;
    };
};
like image 125
Maxim Egorushkin Avatar answered Oct 15 '22 05:10

Maxim Egorushkin


It doesn't answer your question but your comment, How to use operator .:

You may add those two overloads:

template <typename T>
T& get_reference_object(T&& t) { return t; }

template <typename T>
T& get_reference_object(std::reference_wrapper<T> t) { return t.get(); }

and then you may use get_reference_object(arg).foo inside your lambda:

auto monad = [](auto && captive) {
    return [captive = captive](auto&& a) { return get_reference_object(captive).foo(a); };
};

Live example.

like image 32
Jarod42 Avatar answered Oct 15 '22 07:10

Jarod42