web-dev-qa-db-fra.com

c ++ lambdas comment capturer le pack de paramètres variadic de l'étendue supérieure

J'étudie les lambdas génériques et modifie légèrement l'exemple, donc mon lambda devrait capturer le pack de paramètres variadiques du lambda supérieur. Donc, fondamentalement, ce qui est donné à lambda supérieur comme (auto&&...) - devrait en quelque sorte être capturé dans [=] bloquer.

(La transmission parfaite est une autre question, je suis curieux, est-ce possible ici?)

#include <iostream>
#include<type_traits>
#include<utility>


// base case
void doPrint(std::ostream& out) {}

template <typename T, typename... Args>
void doPrint(std::ostream& out, T && t, Args && ... args)
{
    out << t << " ";                // add comma here, see below
    doPrint(out, std::forward<Args&&>(args)...);
}

int main()
{
    // generic lambda, operator() is a template with one parameter
    auto vglambda = [](auto printer) {
        return [=](auto&&... ts) // generic lambda, ts is a parameter pack
        {
            printer(std::forward<decltype(ts)>(ts)...);
            return [=] {  // HOW TO capture the variadic ts to be accessible HERE ↓
                printer(std::forward<decltype(ts)>(ts)...); // ERROR: no matchin function call to forward
            }; // nullary lambda (takes no parameters)
        };
    };
    auto p = vglambda([](auto&&...vars) {
        doPrint(std::cout, std::forward<decltype(vars)>(vars)...);
    });
    auto q = p(1, 'a', 3.14,5); // outputs 1a3.14

    //q(); //use the returned lambda "printer"

}
17
barney

Capture parfaite en C++ 2

template <typename ... Args>
auto f(Args&& args){
    return [... args = std::forward<Args>(args)]{
        // use args
    };
}

solution de contournement C++ 17 et C++ 14

En C++ 17, nous pouvons utiliser une solution de contournement avec des tuples:

template <typename ... Args>
auto f(Args&& ... args){
    return [args = std::make_Tuple(std::forward<Args>(args) ...)]()mutable{
        return std::apply([](auto&& ... args){
            // use args
        }, std::move(args));
    };
}

Malheureusement std::apply est C++ 17, en C++ 14 vous pouvez l'implémenter vous-même ou faire quelque chose de similaire avec boost::hana:

namespace hana = boost::hana;

template <typename ... Args>
auto f(Args&& ... args){
    return [args = hana::make_Tuple(std::forward<Args>(args) ...)]()mutable{
        return hana::unpack(std::move(args), [](auto&& ... args){
            // use args
        });
    };
}

Il pourrait être utile de simplifier la solution de contournement par une fonction capture_call:

#include <Tuple>

// Capture args and add them as additional arguments
template <typename Lambda, typename ... Args>
auto capture_call(Lambda&& lambda, Args&& ... args){
    return [
        lambda = std::forward<Lambda>(lambda),
        capture_args = std::make_Tuple(std::forward<Args>(args) ...)
    ](auto&& ... original_args)mutable{
        return std::apply([&lambda](auto&& ... args){
            lambda(std::forward<decltype(args)>(args) ...);
        }, std::Tuple_cat(
            std::forward_as_Tuple(original_args ...),
            std::apply([](auto&& ... args){
                return std::forward_as_Tuple< Args ... >(
                    std::move(args) ...);
            }, std::move(capture_args))
        ));
    };
}

Utilisez-le comme ceci:

#include <iostream>

// returns a callable object without parameters
template <typename ... Args>
auto f1(Args&& ... args){
    return capture_call([](auto&& ... args){
        // args are perfect captured here
        // print captured args via C++17 fold expression
        (std::cout << ... << args) << '\n';
    }, std::forward<Args>(args) ...);
}

// returns a callable object with two int parameters
template <typename ... Args>
auto f2(Args&& ... args){
    return capture_call([](int param1, int param2, auto&& ... args){
        // args are perfect captured here
        std::cout << param1 << param2;
        (std::cout << ... << args) << '\n';
    }, std::forward<Args>(args) ...);
}

int main(){
    f1(1, 2, 3)();     // Call lambda without arguments
    f2(3, 4, 5)(1, 2); // Call lambda with 2 int arguments
}

Voici une implémentation C++ 14 de capture_call:

#include <Tuple>

// Implementation detail of a simplified std::apply from C++17
template < typename F, typename Tuple, std::size_t ... I >
constexpr decltype(auto)
apply_impl(F&& f, Tuple&& t, std::index_sequence< I ... >){
    return static_cast< F&& >(f)(std::get< I >(static_cast< Tuple&& >(t)) ...);
}

// Implementation of a simplified std::apply from C++17
template < typename F, typename Tuple >
constexpr decltype(auto) apply(F&& f, Tuple&& t){
    return apply_impl(
        static_cast< F&& >(f), static_cast< Tuple&& >(t),
        std::make_index_sequence< std::Tuple_size<
            std::remove_reference_t< Tuple > >::value >{});
}

// Capture args and add them as additional arguments
template <typename Lambda, typename ... Args>
auto capture_call(Lambda&& lambda, Args&& ... args){
    return [
        lambda = std::forward<Lambda>(lambda),
        capture_args = std::make_Tuple(std::forward<Args>(args) ...)
    ](auto&& ... original_args)mutable{
        return ::apply([&lambda](auto&& ... args){
            lambda(std::forward<decltype(args)>(args) ...);
        }, std::Tuple_cat(
            std::forward_as_Tuple(original_args ...),
            ::apply([](auto&& ... args){
                return std::forward_as_Tuple< Args ... >(
                    std::move(args) ...);
            }, std::move(capture_args))
        ));
    };
}

capture_call capture les variables par valeur. Le parfait signifie que le constructeur de déplacement est utilisé si possible. Voici un exemple de code C++ 17 pour une meilleure compréhension:

#include <Tuple>
#include <iostream>
#include <boost/type_index.hpp>


// Capture args and add them as additional arguments
template <typename Lambda, typename ... Args>
auto capture_call(Lambda&& lambda, Args&& ... args){
    return [
        lambda = std::forward<Lambda>(lambda),
        capture_args = std::make_Tuple(std::forward<Args>(args) ...)
    ](auto&& ... original_args)mutable{
        return std::apply([&lambda](auto&& ... args){
            lambda(std::forward<decltype(args)>(args) ...);
        }, std::Tuple_cat(
            std::forward_as_Tuple(original_args ...),
            std::apply([](auto&& ... args){
                return std::forward_as_Tuple< Args ... >(
                    std::move(args) ...);
            }, std::move(capture_args))
        ));
    };
}

struct A{
    A(){
        std::cout << "  A::A()\n";
    }

    A(A const&){
        std::cout << "  A::A(A const&)\n";
    }

    A(A&&){
        std::cout << "  A::A(A&&)\n";
    }

    ~A(){
        std::cout << "  A::~A()\n";
    }
};

int main(){
    using boost::typeindex::type_id_with_cvr;

    A a;
    std::cout << "create object end\n\n";

    [b = a]{
        std::cout << "  type of the capture value: "
          << type_id_with_cvr<decltype(b)>().pretty_name()
          << "\n";
    }();
    std::cout << "value capture end\n\n";

    [&b = a]{
        std::cout << "  type of the capture value: "
          << type_id_with_cvr<decltype(b)>().pretty_name()
          << "\n";
    }();
    std::cout << "reference capture end\n\n";

    [b = std::move(a)]{
        std::cout << "  type of the capture value: "
          << type_id_with_cvr<decltype(b)>().pretty_name()
          << "\n";
    }();
    std::cout << "perfect capture end\n\n";

    [b = std::move(a)]()mutable{
        std::cout << "  type of the capture value: "
          << type_id_with_cvr<decltype(b)>().pretty_name()
          << "\n";
    }();
    std::cout << "perfect capture mutable lambda end\n\n";

    capture_call([](auto&& b){
        std::cout << "  type of the capture value: "
          << type_id_with_cvr<decltype(b)>().pretty_name()
          << "\n";
    }, std::move(a))();
    std::cout << "capture_call perfect capture end\n\n";
}

Production:

  A::A()
create object end

  A::A(A const&)
  type of the capture value: A const
  A::~A()
value capture end

  type of the capture value: A&
reference capture end

  A::A(A&&)
  type of the capture value: A const
  A::~A()
perfect capture end

  A::A(A&&)
  type of the capture value: A
  A::~A()
perfect capture mutable lambda end

  A::A(A&&)
  type of the capture value: A&&
  A::~A()
capture_call perfect capture end

  A::~A()

Le type de la valeur de capture contient && dans le capture_call version car nous devons accéder à la valeur dans le tuple interne via une référence, tandis qu'une capture prise en charge par un langage prend en charge l'accès direct à la valeur.

29
Benjamin Buch

La transmission parfaite est une autre question, je suis curieux, est-ce possible ici?

Eh bien ... il me semble que la transmission parfaite est la question.

La capture de ts... Fonctionne bien et si vous changez, dans le lambda intérieur,

printer(std::forward<decltype(ts)>(ts)...);

avec

printer(ts...);

le programme compile.

Le problème est qu'en capturant ts... Par valeur (en utilisant [=]), Ils deviennent const valeurs et printer() (c'est un lambda qui reçoivent auto&&...vars) reçoivent des références (& ou &&).

Vous pouvez voir le même problème avec les fonctions suivantes

void bar (int &&)
 { }

void foo (int const & i)
 { bar(std::forward<decltype(i)>(i)); }

De clang ++ je reçois

tmp_003-14,gcc,clang.cpp:21:4: error: no matching function for call to 'bar'
 { bar(std::forward<decltype(i)>(i)); }
   ^~~
tmp_003-14,gcc,clang.cpp:17:6: note: candidate function not viable: 1st argument
      ('const int') would lose const qualifier
void bar (int &&)
     ^

Une autre façon de résoudre votre problème consiste à capturer le ts... Comme référence (donc [&]) À la place comme valeur.

3
max66