Pytanie Uogólnione przechwytywanie lambda na pakietach paramatycznych?


W C ++ 14, uogólnione przechwytywanie lambda pozwala nam:

template<class T>
auto pack(T t)
{
    return [t=std::move(t)](auto&& f){f(t);};
};

Ale nie gra z pakietem param:

template<class... T>
auto pack(T... t)
{
    return [t=std::move(t)...](auto&& f){f(t...);};
};

Czy istnieje jakaś specjalna składnia lub dalsza standardowa propozycja rozwiązania tego problemu?


14
2017-07-18 02:53


pochodzenie


Jaki masz problem z przechwytywaniem & i za pomocą f(std::move(t)...)? - chris
@chris Referencje zwisają tak szybko, jak tylko pack zwraca, nie? - Brian
@Brian, Oh yeah, dobry punkt. Nie zauważyłem, że został zwrócony. - chris
Jest przeznaczony do zastąpienia krotki w odpowiedzi na oddzwonienie, więc nie mogę użyć referencji, która będzie zwisała. - Jamboree


Odpowiedzi:


Mój szkic C ++ 14 mówi ([expr.prim.lambda] / 24):

ZA proste przechwytywanie a następnie elipsa jest rozszerzeniem paczki (14.5.3). Na init-capture a następnie elipsa jest źle sformułowana.

Wygląda więc na to, że nie ma sposobu na wykonanie uogólnionego wychwytu variadic. Możliwym rozwiązaniem jest przechwycenie argumentów w krotce, a następnie skorzystanie z jednego z proponowanych rozwiązań: "rozpakowywanie" krotki, aby wywołać odpowiedni wskaźnik funkcji

auto pack(T... t)
{
    return [args=make_tuple(std::move(t)...)](auto&& f){
               // find a way to call f with args
           };
};

EDYTOWAĆ:

To jest teraz głosowane na C ++ 20, wykonane przez to wniosek. Chociaż składnia jest nieco inna:

template<class... T>
auto pack(T... t)
{
    return [...t=std::move(t)](auto&& f){f(t...);};
};

Zauważ, że ... jest przed pobraniem init.


17
2017-07-18 03:27



"Wychwytywanie init po elipsie jest źle sformułowane", więc autor szkicu jest świadomy tego możliwego użycia i nie chce go wspierać? hmm ... - Jamboree
@Jamboree Tak sądzę. Być może są pewne problemy techniczne, które sprawiają, że trudno go poprzeć, a może ktokolwiek złożył propozycję przechwytywania init, chciał, aby było to proste. - Brian
@Jamboree Oto dyskusja: groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/... - Brian
"// znajdź sposób na wywołanie f z argumentami", w C ++ 17 możesz użyć std::apply w związku z tym problem ten można rozwiązać przez: 1) przechwycenie krotki zgodnie z propozycją, 2) napisanie lambda variadic z kodem, który chcesz wykonać, 3) std::apply krotka na lambda. - gnzlbg


Jako kontynuację, doszedłem do tego obejścia:

template<class T>
struct mover
{
    mover(T const& val) : val(val) {}

    mover(T&& val) : val(std::move(val)) {}

    mover(mover const& other) = default;

    mover(mover&& other) = default; 

    mover(mover& other) : val(std::move(other.val)) {}

    operator T const&() const
    {
        return val; 
    }

    T val;
};

template<class T>
using wrap_t = typename std::conditional
    <
        std::is_move_constructible<T>::value
    && !std::is_trivially_copy_constructible<T>::value
      , mover<T>
      , T
    >::type;

template<class... Ts>
auto pack_impl(wrap_t<Ts>... ts)
{
    return [=](auto&& f)->decltype(auto)
    {
        return f(static_cast<Ts const&>(ts)...);
    };
}

auto pack = [](auto&&... ts)
{
    return pack_impl<std::decay_t<decltype(ts)>...>(static_cast<decltype(ts)>(ts)...);
};

Wykorzystuje mover jako serwer proxy, który pozwala lambda na przechwytywanie go za pomocą ruchu (jest nieco odstraszający). I wrap_t decyduje, kiedy jest to potrzebne lub korzystne do zastosowania mover.

Teraz możemy to przetestować:

struct A
{
    A() = default;

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

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

A a;
std::cout <<"p1------------\n";
auto p1 = pack(std::move(a));
std::cout <<"p2------------\n";
auto p2 = std::move(p1);
std::cout <<"p3------------\n";
auto p3 = p2;

Wydrukuje:

p1------------
move
move
p2------------
move
p3------------
copy

3
2017-07-19 06:29





To rozszerza mój komentarz do odpowiedzi Briana powyżej. W C ++ 14 z podstawami biblioteki TS możesz:

template<class... T>
auto pack(T... t)
{
    return [ts = std::make_tuple(std::move(t)...)](auto&& f){
        std::experimental::apply(f, ts);
    };
};

Przypuśćmy, że chcesz generalnie przechwycić pakiet parametrów przez ruch i użyć go w lambda, możesz napisać kod w lambda w obrębie lambda, a następnie zastosować argumenty na nim:

[ts = std::make_tuple(std::move(t)...)](/* args */){
    auto lambda = [&](auto&&... args) {
      // - args is the original parameter pack that one wanted to 
      //   capture by move
      // - the other arguments can be used by reference
    };
    return std::experimental::apply(lambda, ts);
};

3
2018-02-02 09:05