Search code examples
c++lambdadecltypeperfect-forwarding

Perfect forwaring of auto&& in generic lambda


The next generic lambda is used to protect any operation which shares resource between threads:

    auto mutexed = [mtx(std::mutex{})](auto &&fn, auto &&...args) mutable {
        std::unique_lock lo(mtx);
        return fn(args...);
        //return std::forward<decltype(fn)>(fn)(std::forward<decltype(args)>(args)...);
    };

decltype(fn) is not valid for perfect forwarding. It is good for values and rvalues references. But not for lvalue refs.
Writing decltype((fn)) will work with lvalue references but not with rvalues.

So the question.

How to make it to perfectly forward both reference and rvalue reference?

Example to make threads do not corrupt stdout: https://gcc.godbolt.org/z/KsWM6Pq6x

#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
using namespace std::chrono_literals;
using std::cout, std::clog, std::cerr, std::endl;

int main() {
    cout << "Hello, World!" << endl;

    auto mutexed = [mtx(std::mutex{})](auto &&fn, auto &&...args) mutable {
        std::unique_lock lo(mtx);
        //return fn(args...);
        return std::forward<decltype(fn)>(fn)(std::forward<decltype(args)>(args)...);
    };
    
    int counter = 0;
    auto t1 = std::thread( [&](){ 
        while(1){ 
            std::this_thread::sleep_for(10ms);
            // mutexed( [](int&&cnt){ cout << cnt++ << endl; }, std::move(counter) ); //FAILS HERE
            mutexed( [](int&cnt){ cout << cnt++ << endl; }, counter );  //lval ref works fine
        }
    });

    auto t2 = std::thread([&]{ 
        while(1){
        std::this_thread::sleep_for(10ms);
        mutexed( []{ cout << "done long_operation_2" << endl;});}
    });

    t1.join();
    t2.join();
}

Working solution in C++11,14,17 and C++20,23

int main() {
    auto lambda20 = []<class F, class...Ts>(F &&fn, Ts &&...args) {
        return std::forward<F>(fn)(std::forward<Ts>(args)...);
    };
    auto lambda14 = [](auto &&fn, auto &&...args) {
        return std::forward<
                std::conditional_t<
                        std::is_rvalue_reference_v<decltype(fn)>,
                        typename std::remove_reference_t<decltype(fn)>,
                        decltype(fn)>
        >(fn)(
                std::forward<
                        std::conditional_t<std::is_rvalue_reference<decltype(args)>::value,
                                typename std::remove_reference<decltype(args)>::type,
                                decltype(args)
                        >>(args)...);
    };
    int inter = 20;
    lambda20([](int x) { cout << "asdf20   x" << endl; }, inter);
    lambda20([](int &x) { cout << "asdf20  &x" << endl; }, inter);
    lambda20([](int &&x) { cout << "asdf20 &&x" << endl; }, std::move(inter));
    lambda14([](int x) { cout << "asdf14   x" << endl; }, inter);
    lambda14([](int &x) { cout << "asdf14  &x" << endl; }, inter);
    lambda14([](int &&x) { cout << "asdf14 &&x" << endl; }, std::move(inter));

    return 0;
}

C++ pre-20 solution 8 years old by Scott Meyers https://scottmeyers.blogspot.com/2013/05/c14-lambdas-and-perfect-forwarding.html


Solution

  • In C++20 and later

    auto lambda20 = []<class F, class...Ts>(F &&fn, Ts &&...args) {
        return std::forward<F>(fn)(std::forward<Ts>(args)...);
    };
    

    In C++pre20 : C++11,14,17

    auto lambda14 = [](auto &&fn, auto &&...args) {
        return std::forward<
                std::conditional_t<
                        std::is_rvalue_reference_v<decltype(fn)>,
                        typename std::remove_reference_t<decltype(fn)>,
                        decltype(fn)>
        >(fn)(
                std::forward<
                        std::conditional_t<std::is_rvalue_reference<decltype(args)>::value,
                                typename std::remove_reference<decltype(args)>::type,
                                decltype(args)
                        >>(args)...);
    };
    

    Example

    #include <iostream>
    using namespace std;
    int main() {
        auto lambda20 = []<class F, class...Ts>(F &&fn, Ts &&...args) {
            return std::forward<F>(fn)(std::forward<Ts>(args)...);
        };
        auto lambda14 = [](auto &&fn, auto &&...args) {
            return std::forward<
                    std::conditional_t<
                            std::is_rvalue_reference_v<decltype(fn)>,
                            typename std::remove_reference_t<decltype(fn)>,
                            decltype(fn)>
            >(fn)(
                    std::forward<
                            std::conditional_t<std::is_rvalue_reference<decltype(args)>::value,
                                    typename std::remove_reference<decltype(args)>::type,
                                    decltype(args)
                            >>(args)...);
        };
        int inter = 20;
        lambda20([](int x) { cout << "asdf20   x" << endl; }, inter);
        lambda20([](int &x) { cout << "asdf20  &x" << endl; }, inter);
        lambda20([](int &&x) { cout << "asdf20 &&x" << endl; }, std::move(inter));
        lambda14([](int x) { cout << "asdf14   x" << endl; }, inter);
        lambda14([](int &x) { cout << "asdf14  &x" << endl; }, inter);
        lambda14([](int &&x) { cout << "asdf14 &&x" << endl; }, std::move(inter));
    
        return 0;
    }
    

    C++ pre-20 solution 8 years old by Scott Meyers https://scottmeyers.blogspot.com/2013/05/c14-lambdas-and-perfect-forwarding.html