17

In C++14, generalized lambda capture let us do:

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

But it doesn't play with param-pack:

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

Is there any special syntax or further standard proposal to address this?

Jamboree
  • 5,139
  • 2
  • 16
  • 36

3 Answers3

20

My draft of C++14 says ([expr.prim.lambda]/24):

A simple-capture followed by an ellipsis is a pack expansion (14.5.3). An init-capture followed by an ellipsis is ill-formed.

So it looks like there is no way to do a variadic generalized capture. A possible workaround is to just capture the arguments in a tuple and then use one of the solutions suggested here: "unpacking" a tuple to call a matching function pointer

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

EDIT:

It's now voted into C++20, made by this proposal. Although the syntax is a bit different:

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

Note that the ... is before the init-capture.

Jamboree
  • 5,139
  • 2
  • 16
  • 36
Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • 1
    "An init-capture followed by an ellipsis is ill-formed", so the draft author is aware of this possible usage and doesn't want to support it? hmm... – Jamboree Jul 18 '14 at 10:15
  • @Jamboree I suppose so. Maybe there are some technical issues that would make it hard to support, or maybe whoever submitted the proposal for init-captures wanted to keep it simple. – Brian Bi Jul 18 '14 at 16:45
  • 3
    @Jamboree Here's a discussion: https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/ePRzn4K7VcM – Brian Bi Jul 18 '14 at 16:47
  • 1
    " // find a way to call f with args", in C++17 you can use `std::apply` for that, so this problem can be solved by: 1) capturing into a tuple as you propose, 2) writing a variadic lambda with the code you want to execute, 3) `std::apply` the tuple on the lambda. – gnzlbg Feb 02 '16 at 09:02
4

This expands on my comment to Brian answer above. In C++14 with the library fundamentals TS you can:

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

Supposing you want to generically capture a parameter pack by move and use it within a lambda you can write the code in a lambda within a lambda, and then apply the arguments on it:

[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);
};
gnzlbg
  • 7,135
  • 5
  • 53
  • 106
3

As a follow-up, I came to this workaround:

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)...);
};

It utilizes mover as a proxy, which allows lambda to capture it by move (it's a bit hacky). And wrap_t decides when is needed or beneficial to apply mover.

Now we can test it:

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;

Will print:

p1------------
move
move
p2------------
move
p3------------
copy
Jamboree
  • 5,139
  • 2
  • 16
  • 36