while doing one task I came up across this tricky implementation that would allow you to make constexpr lambdas (which is not allowed out of the box):
Crazy constexpr lambdas implementation article
It basicly boils down to this implementation:
template<class F>
struct wrapper
{
//static_assert(std::is_empty<F>(), "Lambdas must be empty");
template<class... Ts>
decltype(auto) operator()(Ts&&... xs) const
{
return reinterpret_cast<const F&>(*this)(std::forward<Ts>(xs)...);
}
};
struct wrapper_factor
{
template<class F>
constexpr wrapper<F> operator += (F*)
{
return{};
}
};
struct addr_add
{
template<class T>
friend typename std::remove_reference<T>::type* operator+(addr_add, T &&t)
{
return &t;
}
};
#define STATIC_LAMBDA wrapper_factor() += true ? nullptr : addr_add() + []
usage
const constexpr auto add_one = STATIC_LAMBDA (/*i believe you can pass arguments here but i havent tried*/) -> bool
{
//do stuff
return stuff;
};
I have got 2 questions:
I needed to comment out static_assert due to "std::is_empty<_Ty>': no appropriate default constructor available" even without making any instance of this lambda. Anyone knows why?
How does this even work? I followed the flow of all the classes and I understand that
a) this part
true ? nullptr : addr_add() + [] your_lambda
returns a nullptr of a type we want lambda to be (further refered to as "correct_type" )
b) wrapper_factory takes this nullptr of correct_type, constructs wrapper. This wrapper is default initialized.
c) wrapper in operator() forwards the arguments that he is called with (passed in the /* i believe.../* place) to an object that is "wrapper this pointer casted to correct_type".
Now where is the information what routine to call actually passed? I can only see default created empty wrapper that is reinterpreted to some lambda type and called via ().
Best regards
Marcin K.
So consider this code
#include <type_traits>
template<class F>
struct wrapper
{
static_assert(std::is_empty<F>::value, "Lambdas must be empty");
template<class... Ts>
decltype(auto) operator()(Ts&&... xs) const
{
return reinterpret_cast<const F&>(*this)(std::forward<Ts>(xs)...);
}
};
struct wrapper_factor
{
template<class F>
constexpr wrapper<F> operator += (F*)
{
return{};
}
};
struct addr_add
{
template<class T>
friend typename std::remove_reference<T>::type *operator+(addr_add, T &&t)
{
return &t;
}
};
#define STATIC_LAMBDA wrapper_factor() += true ? nullptr : addr_add() + []
const constexpr auto add_one = STATIC_LAMBDA(auto x)
{
return x + 1;
};
int main()
{
int a = 1;
int b = add_one(a);
return 0;
}
So in MSVS2015, add_one is reported as a constexpr wrapper<type> add_one = {}
But in main, add_one(a) is reported as int wrapper<lambda []auto (auto x)->auto>::operator() <int &>(int &xs) const
And you can see that the friend function *operator+ in addr_add returns the pointer to the lambda function. And the lambda function definition is implicit. The variadic template in the wrapper struct operator() will mimic the lambda arguments.
Is this what you wanted to know?
Related
Using C++ 17. I have the following:
template <typename T>
using ptr_t = std::shared_ptr<const T>;
class some_type;
class A { some_type foo() const; }
class B { some_type foo() const; }
class C { some_type foo(int) const; }
std::variant<ptr_t<A>, ptr_t<B>, ptr_t<C>>;
A variant holds shared_ptr(s) to different types. All expected to have function foo() that may be void or take a parameter. I will then have a visitor that would correctly dispatch foo, something like this (conceptually):
struct visitor
{
template <typename T>
ptr_t<some_type> operator()(const T& config) const
{
if constexpr (// determine if foo() of the underlying type of a shared_ptr can be called with int param)
return config->foo(15);
else
return config->foo();
}
is there a way to say this? I tried various ways but can't come with something that compiles. Template parameter, T, is ptr_t<A|B|C>.
std::is_invocable_v<Callable, Args...> is the way to go. Unfortunatelly, it will not compile just like that with if constexpr. It will either fail because "there is no operator()() overload", or there is no overload for operator taking Args....
I suggest you add a wrapper class for a callable and use it with a specialized alias template of std::variant instead of writing your own visitor. It will allow you to use std::visit seamlessly.
#include <type_traits>
#include <variant>
template <typename Callable>
class wrapped_callable
{
Callable c;
public:
wrapped_callable(Callable c)
: c(c)
{}
template <typename ... Args>
constexpr decltype(auto) operator()(Args &&... args) const
{
return _invoke(std::is_invocable<Callable, Args...>{}, c, std::forward<Args>(args)...);
}
private:
using _invocable = std::true_type;
using _non_invocable = std::false_type;
template <typename T, typename ... Args>
constexpr static decltype(auto) _invoke(_invocable, const T& t, Args &&... args)
{
return t(std::forward<Args>(args)...);
}
template <typename T, typename ... Args>
constexpr static decltype(auto) _invoke(_non_invocable, const T& t, Args ... args)
{
return t();
}
};
template <typename ... T>
using variant_callable = std::variant<wrapped_callable<T>...>;
struct int_callable
{
int operator()(int i) const
{
return i;
}
};
struct non_callable
{
int operator()() const
{
return 42;
}
};
#include <iostream>
int main()
{
using variant_t = variant_callable<int_callable, non_callable>;
// 23 is ignored, 42 is printed
std::visit([](const auto &callable){
std::cout << callable(23) << '\n';
}, variant_t{non_callable()});
// 23 is passed along and printed
std::visit([](const auto &callable){
std::cout << callable(23) << '\n';
}, variant_t{int_callable()});
}
Program returned: 0
42
23
https://godbolt.org/z/e6GzvW6n6
But The idea is not to have any specialization for all types in a variant as it will then require changing the visitor code every time a new type is added.
That is what template alias of std::variant<wrapped_callable<T>...> for. You just add append a new type to the list, that's it.
Take notice, that it does not depend on if constexpr. So if you manage to provide your own variant and is_invocable_v, it will work for C++14. For C++11 possibly, but some modifications regarding constexpr functions might be needed.
Of course you can implement your visitor in the same manner if you want to use std::shared_ptr istead of a callable.
But I don't see any reason to use:
visitor + smart pointer. Just use a smart pointer - it will give you runtime polymorphism in a "classic" way (via virtual inheritence)
why std::shared_ptr? Do you really need to share the ownership? Just stick with std::unique_ptr
I have a function wrapper for use over the network:
#pragma once
#include <tuple>
#include <functional>
struct ZPackage {
std::unique_ptr<int> m_dummy;
template<typename T>
T Read() {
T t = T();
return t;
}
};
class ZRpc;
template <class C, class Tuple, class F, size_t... Is>
constexpr auto invoke_tuple_impl(F f, C& c, ZRpc* rpc, Tuple t, std::index_sequence<Is...>) {
return std::invoke(f, c, rpc, std::move(std::get<Is>(t))...);
}
template <class C, class Tuple, class F>
constexpr void invoke_tuple(F f, C& c, ZRpc* rpc, Tuple t) {
invoke_tuple_impl(f, c, rpc, std::move(t),
std::make_index_sequence < std::tuple_size<Tuple>{} > {}); // last arg is for template only
}
class ZRpcMethodBase
{
public:
virtual void Invoke(ZRpc* pao, ZPackage& pkg) = 0;
};
template<class C, class...Args>
class ZRpcMethod final : public ZRpcMethodBase {
using Lambda = void(C::*)(ZRpc*, Args...);
C* object;
Lambda lambda;
template<class F>
auto Invoke_impl(ZPackage& pkg) {
return std::tuple(pkg.Read<F>());
}
// Split a param,
// Add that param from Packet into tuple
template<class F, class S, class...R>
auto Invoke_impl(ZPackage& pkg) {
auto a(Invoke_impl(pkg));
std::tuple<S, R...> b = Invoke_impl<S, R...>(pkg);
return std::tuple_cat(a, b);
}
public:
ZRpcMethod(C* object, Lambda lam) : object(object), lambda(lam) {}
void Invoke(ZRpc* rpc, ZPackage& pkg) override {
// Invoke_impl returns a tuple of types by recursion
if constexpr (sizeof...(Args))
{
auto tupl = Invoke_impl<Args...>(pkg);
invoke_tuple(lambda, object, rpc, tupl);
}
else
{
// works like ~magic~
std::invoke(lambda, object, rpc);
}
}
};
I have added in some of the types that are utilized, ZRpc, ZPackage, and an example Object.
I am struggling with getting this wrapper to work with types that have a deleted copy constructor, such as std::unique_ptr (or in this example the ZPackage which contains the std::unique_ptr. The specific error I get:
std::tuple::tuple(const std::tuple &)': attempting to reference a deleted function
#include "TestRpc.h"
class ZRpc { };
struct Object {
void ok_method(ZRpc* rpc, int i) {
}
void broken_method(ZRpc* rpc, ZPackage pkg) {
}
};
int main() {
ZRpc rpc;
Object obj;
// compiles fine
auto a = new ZRpcMethod(&obj, &Object::ok_method);
// does not compile
auto b = new ZRpcMethod(&obj, &Object::broken_method);
}
I doubt that it will make a difference, but just for reference, here are some things I have tried and commented out previously with no avail: https://pastebin.com/aHSsLzWe. I am unable to wrap my head around variadic templates and how to correctly forward.
How can I achieve perfect forwarding with move-only constructor types?
EDIT:
I changed the std::forward to move
In the Invoke() function body, since if constexpr (sizeof...(Args)) == 1 is true, this will invoke Invoke_impl<Args...>(pkg) which will return a std::tuple<ZPackage> which is move-only, so you also need to std::move it into invoke_tuple().
if constexpr (sizeof...(Args)) {
auto tupl = Invoke_impl<Args...>(pkg);
invoke_tuple(lambda, object, rpc, std::move(tupl));
} else {
// ...
}
I'm trying to define a function template<typename T> zero() and specialize it to various cases.
zero<T>() should return
static T::zero() if that exists.
else static_cast<T>(0) if that is defined.
So far so good, as implemented below.
Now I want to extend the template in a sensible way to any T that is implicitly convertible to a type of the form std::function<Y(X...)>, and should return the corresponding zero-returning function [](X...){return zero<Y>();}
What would be the best way to do this?
namespace hidden {
// tag dispatching
template<int r>
struct rank : rank<r - 1> {};
template<>
struct rank<0> {};
template<typename T>
auto zero(rank<2>) -> decltype(T::zero()) {
return T::zero();
}
template<typename T>
auto zero(rank<1>) -> decltype(static_cast<T>(0)) {
return static_cast<T>(0);
}
// This is where I need help
template<typename T>
auto zero(rank<0>) -> std::enable_if_t</* T is implicitly convertible to std::function<Y(X...)> */,T> {
using Y = // the type returned when an instance of T is invoked
return []() {
return zero<Y>();
};
}
}
template<typename T>
auto zero() { return hidden::zero<T>(rank<10>{}); }
Edit:
I've resorted for now to repeating the following with concrete signature for each expected signature:
template<typename T>
auto zero(rank<0>)
-> std::enable_if_t<std::is_assignable<std::function<double(double)>, T>::value
, std::function<double(double)>> {
using Y = double;
return []() {
return zero<Y>();
};
}
but I'm hoping it's possible to replace the copy-pasting with template magic.
In c++17 with a deduction guide of std::function:
template <typename T,
typename F = decltype(std::function{std::declval<T>()}),
typename Y = typename F::result_type>
F zero(rank<0>) {
return [](auto&&...) {
return zero<Y>(rank<10>{});
};
}
DEMO
In c++14 you need to write this trait yourself.
Note, however, that this will not work if T::operator() is overloaded or represents a function template (including generic lambda expressions).
I am trying to implement a resource protection class which would combine data along with a shared mutex (actually, QReadWriteLock, but it's similar). The class must provide the method to apply a user-defined function to the data when the lock is acquired. I would like this apply method to work differently depending on the function parameter (reference, const reference, or value). For example, when the user passes a function like int (const DataType &) it shouldn't block exclusively as we are just reading the data and, conversely, when the function has the signature like void (DataType &) that implies data modification, hence the exclusive lock is needed.
My first attempt was to use std::function:
template <typename T>
class Resource1
{
public:
template <typename Result>
Result apply(std::function<Result(T &)> &&f)
{
QWriteLocker locker(&this->lock); // acquire exclusive lock
return std::forward<std::function<Result(T &)>>(f)(this->data);
}
template <typename Result>
Result apply(std::function<Result(const T &)> &&f) const
{
QReadLocker locker(&this->lock); // acquire shared lock
return std::forward<std::function<Result (const T &)>>(f)(this->data);
}
private:
T data;
mutable QReadWriteLock lock;
};
But std::function doesn't seem to restrict parameter constness, so std::function<void (int &)> can easily accept void (const int &), which is not what I want. Also in this case it can't deduce lambda's result type, so I have to specify it manually:
Resource1<QList<int>> resource1;
resource1.apply<void>([](QList<int> &lst) { lst.append(11); }); // calls non-const version (ok)
resource1.apply<int>([](const QList<int> &lst) -> int { return lst.size(); }); // also calls non-const version (wrong)
My second attempt was to use std::result_of and return type SFINAE:
template <typename T>
class Resource2
{
public:
template <typename F>
typename std::result_of<F (T &)>::type apply(F &&f)
{
QWriteLocker locker(&this->lock); // lock exclusively
return std::forward<F>(f)(this->data);
}
template <typename F>
typename std::result_of<F (const T &)>::type apply(F &&f) const
{
QReadLocker locker(&this->lock); // lock non-exclusively
return std::forward<F>(f)(this->data);
}
private:
T data;
mutable QReadWriteLock lock;
};
Resource2<QList<int>> resource2;
resource2.apply([](QList<int> &lst) {lst.append(12); }); // calls non-const version (ok)
resource2.apply([](const QList<int> &lst) { return lst.size(); }); // also calls non-const version (wrong)
Mainly the same thing happens: as long as the object is non-const the mutable version of apply gets called and result_of doesn't restrict anything.
Is there any way to achieve this?
You may do the following
template <std::size_t N>
struct overload_priority : overload_priority<N - 1> {};
template <> struct overload_priority<0> {};
using low_priority = overload_priority<0>;
using high_priority = overload_priority<1>;
template <typename T>
class Resource
{
public:
template <typename F>
auto apply(F&& f) const
// -> decltype(apply_impl(std::forward<F>(f), high_priority{}))
{
return apply_impl(std::forward<F>(f), high_priority{});
}
template <typename F>
auto apply(F&& f)
// -> decltype(apply_impl(std::forward<F>(f), high_priority{}))
{
return apply_impl(std::forward<F>(f), high_priority{});
}
private:
template <typename F>
auto apply_impl(F&& f, low_priority) -> decltype(f(std::declval<T&>()))
{
std::cout << "ReadLock\n";
return std::forward<F>(f)(this->data);
}
template <typename F>
auto apply_impl(F&& f, high_priority) -> decltype(f(std::declval<const T&>())) const
{
std::cout << "WriteLock\n";
return std::forward<F>(f)(this->data);
}
private:
T data;
};
Demo
Jarod has given a workaround, but I'll explain why you cannot achieve that this regular way.
The problem is that:
Overload resolution prefers non-const member functions over const member functions when called from a non-const object
whatever object this signature void foo(A&) can accept, void foo(const A&) can also the same object. The latter even has a broader binding set than the former.
Hence, to solve it, you will have to at least defeat point 1 before getting to 2. As Jarod has done.
From your signatures (see my comment annotations):
template <typename F>
typename std::result_of<F (T &)>::type apply(F &&f) //non-const member function
{
return std::forward<F>(f)(this->data);
}
template <typename F>
typename std::result_of<F (const T &)>::type apply(F &&f) const //const member function
{
return std::forward<F>(f)(this->data);
}
When you call it like:
resource2.apply([](QList<int> &lst) {lst.append(12); }); //1
resource2.apply([](const QList<int> &lst) { return lst.size(); }); //2
First of all, remember that resource2 isn't a const reference. Hence, the non-const membr function of apply will always be prefered by Overload resolution.
Now, taking the case of the first call //1, Whatever that lambda is callable with, then then the second one is also callable with that object
A simplified mock-up of what you are trying to do is:
struct A{
template<typename Func>
void foo(Func&& f); //enable if we can call f(B&);
template<typename Func>
void foo(Func&& f) const; //enable if we can call f(const B&);
};
void bar1(B&);
void bar2(const B&);
int main(){
A a;
a.foo(bar1);
a.foo(bar2);
//bar1 and bar2 can be both called with lvalues
B b;
bar1(b);
bar2(b);
}
As I understand it, you want to discriminate a parameter that's a std::function that takes a const reference versus a non-constant reference.
The following SFINAE-based approach seems to work, using a helper specialization class:
#include <functional>
#include <iostream>
template<typename ...Args>
using void_t=void;
template<typename Result,
typename T,
typename lambda,
typename void_t=void> class apply_helper;
template <typename T>
class Resource1
{
public:
template <typename Result, typename lambda>
Result apply(lambda &&l)
{
return apply_helper<Result, T, lambda>::helper(std::forward<lambda>(l));
}
};
template<typename Result, typename T, typename lambda, typename void_t>
class apply_helper {
public:
static Result helper(lambda &&l)
{
std::cout << "T &" << std::endl;
T t;
return l(t);
}
};
template<typename Result, typename T, typename lambda>
class apply_helper<Result, T, lambda,
void_t<decltype( std::declval<lambda>()( std::declval<T>()))>> {
public:
static Result helper(lambda &&l)
{
std::cout << "const T &" << std::endl;
return l( T());
}
};
Resource1<int> test;
int main()
{
auto lambda1=std::function<char (const int &)>([](const int &i)
{
return (char)i;
});
auto lambda2=std::function<char (int &)>([](int &i)
{
return (char)i;
});
auto lambda3=[](const int &i) { return (char)i; };
auto lambda4=[](int &i) { return (char)i; };
test.apply<char>(lambda1);
test.apply<char>(lambda2);
test.apply<char>(lambda3);
test.apply<char>(lambda4);
}
Output:
const T &
T &
const T &
T &
Demo
The helper() static class in the specialized class can now be modified to take a this parameter, instead, and then use it to trampoline back into the original template's class's method.
As long as the capture lists of your lambdas are empty, you can rely on the fact that such a lambda decays to a function pointer.
It's suffice to discriminate between the two types.
It follows a minimal, working example:
#include<iostream>
template <typename T>
class Resource {
public:
template <typename Result>
Result apply(Result(*f)(T &)) {
std::cout << "non-const" << std::endl;
return f(this->data);
}
template <typename Result>
Result apply(Result(*f)(const T &)) const {
std::cout << "const" << std::endl;
return f(this->data);
}
private:
T data;
};
int main() {
Resource<int> resource;
resource.apply<void>([](int &lst) { });
resource.apply<int>([](const int &lst) -> int { return 42; });
}
template<typename T> void doSomething(T&& mStuff)
{
auto lambda([&mStuff]{ doStuff(std::forward<T>(mStuff)); });
lambda();
}
Is it correct to capture the perfectly-forwarded mStuff variable with the &mStuff syntax?
Or is there a specific capture syntax for perfectly-forwarded variables?
EDIT: What if the perfectly-forwarded variable is a parameter pack?
Is it correct to capture the perfectly-forwarded mStuff variable with
the &mStuff syntax?
Yes, assuming that you don't use this lambda outside doSomething. Your code captures mStuff per reference and will correctly forward it inside the lambda.
For mStuff being a parameter pack it suffices to use a simple-capture with a pack-expansion:
template <typename... T> void doSomething(T&&... mStuff)
{
auto lambda = [&mStuff...]{ doStuff(std::forward<T>(mStuff)...); };
}
The lambda captures every element of mStuff per reference. The closure-object saves an lvalue reference for to each argument, regardless of its value category. Perfect forwarding still works; In fact, there isn't even a difference because named rvalue references would be lvalues anyway.
To make the lambda valid outside the scope where it's created, you need a wrapper class that handles lvalues and rvalues differently, i.e., keeps a reference to an lvalue, but makes a copy of (by moving) an rvalue.
Header file capture.h:
#pragma once
#include <type_traits>
#include <utility>
template < typename T >
class capture_wrapper
{
static_assert(not std::is_rvalue_reference<T>{},"");
std::remove_const_t<T> mutable val_;
public:
constexpr explicit capture_wrapper(T&& v)
noexcept(std::is_nothrow_move_constructible<std::remove_const_t<T>>{})
:val_(std::move(v)){}
constexpr T&& get() const noexcept { return std::move(val_); }
};
template < typename T >
class capture_wrapper<T&>
{
T& ref_;
public:
constexpr explicit capture_wrapper(T& r) noexcept : ref_(r){}
constexpr T& get() const noexcept { return ref_; }
};
template < typename T >
constexpr typename std::enable_if<
std::is_lvalue_reference<T>{},
capture_wrapper<T>
>::type
capture(std::remove_reference_t<T>& t) noexcept
{
return capture_wrapper<T>(t);
}
template < typename T >
constexpr typename std::enable_if<
std::is_rvalue_reference<T&&>{},
capture_wrapper<std::remove_reference_t<T>>
>::type
capture(std::remove_reference_t<T>&& t)
noexcept(std::is_nothrow_constructible<capture_wrapper<std::remove_reference_t<T>>,T&&>{})
{
return capture_wrapper<std::remove_reference_t<T>>(std::move(t));
}
template < typename T >
constexpr typename std::enable_if<
std::is_rvalue_reference<T&&>{},
capture_wrapper<std::remove_reference_t<T>>
>::type
capture(std::remove_reference_t<T>& t)
noexcept(std::is_nothrow_constructible<capture_wrapper<std::remove_reference_t<T>>,T&&>{})
{
return capture_wrapper<std::remove_reference_t<T>>(std::move(t));
}
Example/test code that shows it works. Note that the "bar" example shows how one can use std::tuple<...> to work around the lack of pack expansion in lambda capture initializer, useful for variadic capture.
#include <cassert>
#include <tuple>
#include "capture.h"
template < typename T >
auto foo(T&& t)
{
return [t = capture<T>(t)]()->decltype(auto)
{
auto&& x = t.get();
return std::forward<decltype(x)>(x);
// or simply, return t.get();
};
}
template < std::size_t... I, typename... T >
auto bar_impl(std::index_sequence<I...>, T&&... t)
{
static_assert(std::is_same<std::index_sequence<I...>,std::index_sequence_for<T...>>{},"");
return [t = std::make_tuple(capture<T>(t)...)]()
{
return std::forward_as_tuple(std::get<I>(t).get()...);
};
}
template < typename... T >
auto bar(T&&... t)
{
return bar_impl(std::index_sequence_for<T...>{}, std::forward<T>(t)...);
}
int main()
{
static_assert(std::is_same<decltype(foo(0)()),int&&>{}, "");
assert(foo(0)() == 0);
auto i = 0;
static_assert(std::is_same<decltype(foo(i)()),int&>{}, "");
assert(&foo(i)() == &i);
const auto j = 0;
static_assert(std::is_same<decltype(foo(j)()),const int&>{}, "");
assert(&foo(j)() == &j);
const auto&& k = 0;
static_assert(std::is_same<decltype(foo(std::move(k))()),const int&&>{}, "");
assert(foo(std::move(k))() == k);
auto t = bar(0,i,j,std::move(k))();
static_assert(std::is_same<decltype(t),std::tuple<int&&,int&,const int&,const int&&>>{}, "");
assert(std::get<0>(t) == 0);
assert(&std::get<1>(t) == &i);
assert(&std::get<2>(t) == &j);
assert(std::get<3>(t) == k and &std::get<3>(t) != &k);
}
TTBOMK, for C++14, I think the above solutions for lifetime handling can be simplified to:
template <typename T> capture { T value; }
template <typename T>
auto capture_example(T&& value) {
capture<T> cap{std::forward<T>(value)};
return [cap = std::move(cap)]() { /* use cap.value *; };
};
or more anonymous:
template <typename T>
auto capture_example(T&& value) {
struct { T value; } cap{std::forward<T>(value)};
return [cap = std::move(cap)]() { /* use cap.value *; };
};
Used it here (admittedly, this particular block of code is rather useless :P)
https://github.com/EricCousineau-TRI/repro/blob/3fda1e0/cpp/generator.cc#L161-L176
Yes you can do perfect capturing, but not directly. You will need to wrap the type in another class:
#define REQUIRES(...) class=std::enable_if_t<(__VA_ARGS__)>
template<class T>
struct wrapper
{
T value;
template<class X, REQUIRES(std::is_convertible<T, X>())>
wrapper(X&& x) : value(std::forward<X>(x))
{}
T get() const
{
return std::move(value);
}
};
template<class T>
auto make_wrapper(T&& x)
{
return wrapper<T>(std::forward<T>(x));
}
Then pass them as parameters to a lambda that returns a nested lambda that captures the parameters by value:
template<class... Ts>
auto do_something(Ts&&... xs)
{
auto lambda = [](auto... ws)
{
return [=]()
{
// Use `.get()` to unwrap the value
some_other_function(ws.get()...);
};
}(make_wrapper(std::forward<Ts>(xs)...));
lambda();
}
Here's a solution for C++17 that uses deduction guides to make it easy. I'm elaborating on Vittorio Romeo's (the OP) blog post, where he provides a solution to his own question.
std::tuple can be used to wrap the perfectly forwarded variables, making a copy or keeping a reference of each of them on a per-variable basis, as needed. The tuple itself is value-captured by the lambda.
To make it easier and cleaner, I'm going to create a new type derived from std::tuple, so to provide guided construction (that will let us avoid the std::forward and decltype() boilerplate) and pointer-like accessors in case there's just one variable to capture.
// This is the generic case
template <typename... T>
struct forwarder: public std::tuple<T...> {
using std::tuple<T...>::tuple;
};
// This is the case when just one variable is being captured.
template <typename T>
struct forwarder<T>: public std::tuple<T> {
using std::tuple<T>::tuple;
// Pointer-like accessors
auto &operator *() {
return std::get<0>(*this);
}
const auto &operator *() const {
return std::get<0>(*this);
}
auto *operator ->() {
return &std::get<0>(*this);
}
const auto *operator ->() const {
return &std::get<0>(*this);
}
};
// std::tuple_size needs to be specialized for our type,
// so that std::apply can be used.
namespace std {
template <typename... T>
struct tuple_size<forwarder<T...>>: tuple_size<tuple<T...>> {};
}
// The below two functions declarations are used by the deduction guide
// to determine whether to copy or reference the variable
template <typename T>
T forwarder_type(const T&);
template <typename T>
T& forwarder_type(T&);
// Here comes the deduction guide
template <typename... T>
forwarder(T&&... t) -> forwarder<decltype(forwarder_type(std::forward<T>(t)))...>;
And then one can use it like following.
The variadic version:
// Increment each parameter by 1 at each invocation and print it.
// Rvalues will be copied, Lvalues will be passed as references.
auto variadic_incrementer = [](auto&&... a)
{
return [a = forwarder(a...)]() mutable
{
std::apply([](auto &&... args) {
(++args._value,...);
((std::cout << "variadic_incrementer: " << args._value << "\n"),...);
}, a);
};
};
The non-variadic version:
// Increment the parameter by 1 at each invocation and print it.
// Rvalues will be copied, Lvalues will be passed as references.
auto single_incrementer = [](auto&& a)
{
return [a = forwarder(a)]() mutable
{
++a->_value;
std::cout << "single_incrementer: " << a->_value << "\n";
};
};