C++11 "Overloaded Lambda" with Variadic Template and Variable Capture

C++11 overloaded lambda with variadic template and variable capture

Overload resolution works only for functions that exist in a common scope. This means that the second implementation fails to find the second overload because you don't import function call operators from overload<Frest...> into overload<F0, Frest...>.

However, a non-capturing lambda type defines a conversion operator to a function pointer with the same signature as the lambda's function call operator. This conversion operator can be found by name lookup, and this is what gets invoked when you remove the capturing part.

The correct implementation, that works for both capturing and non-capturing lambdas, and that always calls operator() instead of a conversion operator, should look as follows:

template <class... Fs>
struct overload;

template <class F0, class... Frest>
struct overload<F0, Frest...> : F0, overload<Frest...>
{
overload(F0 f0, Frest... rest) : F0(f0), overload<Frest...>(rest...) {}

using F0::operator();
using overload<Frest...>::operator();
};

template <class F0>
struct overload<F0> : F0
{
overload(F0 f0) : F0(f0) {}

using F0::operator();
};

template <class... Fs>
auto make_overload(Fs... fs)
{
return overload<Fs...>(fs...);
}

DEMO

In c++17, with class template argument deduction and pack expansion of using declarations in place, the above implementation can be simplified to:

template <typename... Ts> 
struct overload : Ts... { using Ts::operator()...; };

template <typename... Ts>
overload(Ts...) -> overload<Ts...>;

DEMO 2

Move (or copy) capture variadic template arguments into lambda

To be clear, I don't want to perfectly capture the arguments as in c++
lambdas how to capture variadic parameter pack from the upper scope I
want to move them if possible

Just using the same form:

auto lambda = [fn, ...args = std::move(args)]() mutable {
(*fn)(std::move(args)...);
};

In C++17, you could do:

auto lambda = [fn, args = std::tuple(std::move(args)...)]() mutable {
std::apply([fn](auto&&... args) { (*fn)( std::move(args)...); },
std::move(args));
};

C++11 lambdas and parameter packs

I thought part of the point of using rvalue references this way was that they were supposed to forward perfectly without needing multiple overloads?

No, rvalue references and forwarding references are two different things. They both involve && but they behave differently. Forwarding references occur only in the presence of template argument deduction (or auto type deduction, which uses the same rules).

This is a forwarding reference:

template<typename T>
void foo(T&& v);

Template argument deduction will let T be any type; if T is an lvalue reference then v is an lvalue reference, otherwise v is an rvalue reference, due to reference collapsing.

This is NOT a forwarding reference:

void foo(int&& v);

There is no template argument deduction here, so v is a plain rvalue reference to int, it can bind only to rvalues.

This is also NOT a forwarding reference:

template<typename T>
struct Foo
{
void bar(T&& v);
};

Again because there is no template argument deduction. The function Foo<int>::bar is not a template, it is a plain function taking a plain rvalue reference parameter which can bind only to rvalues. The function Foo<int&>::bar is a plain function taking a plain lvalue reference parameter which can bind only to lvalues.

On a side note, I originally tried to use decltype([](Params&&...){}) as the return type, as this seems more natural.

That would never work. Every lambda expression denotes a unique type, so decltype([]{}) and decltype([]{}) are two different, unrelated types.

Here is a possible solution. The only C++14 feature used is std::index_sequence, for which you can Google a C++11 implementation.

template<typename... Ts>
struct MoveTupleWrapper
{
MoveTupleWrapper(std::tuple<Ts...>&& tuple) : m_tuple(tuple) {}
MoveTupleWrapper(MoveTupleWrapper& other) : m_tuple(std::move(other.m_tuple)) {}
MoveTupleWrapper& operator=(MoveTupleWrapper& other) { m_tuple = std::move(other.m_tuple); }
std::tuple<Ts...> m_tuple;

template<typename T, typename... Params>
void apply(void (T::*member)(Params...), T& object)
{
applyHelper(member, object, std::index_sequence_for<Ts...>);
}
template<typename T, typename... Params, size_t... Is>
void applyHelper(void (T::*member)(Params...), T& object, std::index_sequence<Is...>)
{
(object.*member)(std::move(std::get<Is>(m_tuple))...);
}
};

template<typename... Ts>
auto MoveTuple(Ts&&... objects)
-> MoveTupleWrapper<std::decay_t<Ts>...>
{
return std::make_tuple(std::forward<Ts>(objects)...);
}

template<typename T, typename... Params>
struct WeakTaskPoster
{
WeakTaskPoster(const boost::weak_ptr<IWorkQueue>& queue,
void (T::*member)(Params...),
const boost::weak_ptr<T>& weak) :
m_queue(queue),
m_member(member),
m_weak(weak)
{}
boost::weak_ptr<IWorkQueue> m_queue;
void (T::*m_member)(Params...);
boost::weak_ptr<T> m_weak;

template<typename... XParams>
void operator()(XParams&&... params) const
{
if (auto qp = m_queue.lock())
{
auto weak = m_weak;
auto member = m_member;
auto tuple = MoveTuple(std::forward<XParams>(params)...);
qp->Post([weak, member, tuple]() mutable
{
if (auto strong = weak.lock())
{
tuple.apply(member, *strong);
}
});
}
}
};

template<typename T, typename... Params>
auto PostWeakTask(const boost::weak_ptr<IWorkQueue>& queue,
void (T::*member)(Params...),
const boost::weak_ptr<T>& weak)
-> WeakTaskPoster<T, Params...>
{
return { queue, member, weak };
}

Note that WeakTaskPoster::operator() is a template, so we obtain perfect forwarding. The C++14 way to do that would've been a generic lambda. Then there is this MoveTuple thing. That's a wrapper around a std::tuple which simply implements copy in terms of move, so we can work around the lack of C++14 lambda capture-by-move. It also implements exactly the subset of std::apply that is necessary.

It's very similar to Yakk's answer, except that the tuple of parameters is moved instead of copied into the lambda's capture set.

Does lambda capture support variadic template arguments

It's a bug in gcc. See [c++0x]lambdas and variadic templates don't work together or perhaps [C++11] Pack expansion fails in lambda expressions.

c++14 Variadic lambda capture for function binding

I've read that lambdas can be inlined while std::bind cannot inline
because it takes place through a call to a function pointer.

If you pass simpleAdd to something that then binds the arguments, then whether you use bind or not doesn't matter. What do you think the lambda captures with func? It's a function pointer.

The lambda-vs-function-pointer case is about writing bind(simpleAdd, 2, 3) vs. [] { return simpleAdd(2, 3); }. Or binding a lambda like [](auto&&...args) -> decltype(auto) { return simpleAdd(decltype(args)(args)...); } vs. binding simpleAdd directly (which will use a function pointer).


In any event, implementing it is surprisingly tricky. You can't use by-reference capture because things can easily get dangling, you can't use a simple by-value capture because that would always copy the arguments even for rvalues, and you can't do a pack expansion in an init-capture.

This follows std::bind's semantics (invoking the function object and passing all bound arguments as lvalues) except that 1) it doesn't handle placeholders or nested binds, and 2) the function call operator is always const:

template<class Func, class ...Args>
inline decltype(auto) funcLambda(Func && func, Args && ...args)
{
return [func = std::forward<Func>(func),
args = std::make_tuple(std::forward<Args>(args)...)] {
return std::experimental::apply(func, args);
};
}

cppreference has an implementation of std::experimental::apply.

Note that this does unwrap reference_wrappers, like bind, because make_tuple does it.

Your original code breaks down because args are const in the lambda's function call operator (which is const by default), and the forward ends up attempting to cast away constness.

Overload a lambda function

No, you can not overload the lambda!

The lambdas are anonymous functors(i.e. unnamed function objects), and not simple functions. Therefore, overloading those objects not possible.
What you basically trying to do is almost

struct <some_name>
{
int operator()(int idx) const
{
return {}; // some int
}
}translate; // >>> variable name

struct <some_name>
{
int operator()(char idx) const
{
return {}; // some int
}
}translate; // >>> variable name

Which is not possible, as the same variable name can not be reused in C++.


However, in c++17 we have if constexpr by which one can instantiate the only branch which is true at compile time.

Meaning the possible solutions are:

  • A single variabe template lambda. or
  • A generic lambda and find the type of the parameter using decltype
    for the if constexpr check.(credits @NathanOliver)

Using variabe template you can do something like. (See a live demo online)

#include <type_traits> // std::is_same_v

template<typename T>
constexpr auto translate = [](T idx)
{
if constexpr (std::is_same_v<T, int>)
{
constexpr static int table[8]{ 7,6,5,4,3,2,1,0 };
return table[idx];
}
else if constexpr (std::is_same_v<T, char>)
{
std::map<char, int> table{ {'a', 0}, {'b', 1}, {'c', 2}, {'d', 3}, {'e', 4}, {'f', 5}, {'g', 6}, {'h', 7} };
return table[idx];
}
};

and call it like

int r = translate<int>(line[0]);
int c = translate<char>(line[1]);

Using generic lambda(since c++14), the above will be: (See a live demo online)

#include <type_traits> // std::is_same_v

constexpr auto translate = [](auto idx)
{
if constexpr (std::is_same_v<decltype(idx), int>)
{
constexpr static int table[8]{ 7,6,5,4,3,2,1,0 };
return table[idx];
}
else if constexpr (std::is_same_v<decltype(idx), char>)
{
std::map<char, int> table{ {'a', 0}, {'b', 1}, {'c', 2}, {'d', 3}, {'e', 4}, {'f', 5}, {'g', 6}, {'h', 7} };
return table[idx];
}
};

and call the lambda as you do now:

int r = translate(static_cast<int>(line[0]));
int c = translate(static_cast<char>(line[1]));

Callback with variadic template to ambiguous overloads

To follow up Howard's fine answer, let me just state that in the end I conclude that making the sendBarToID function templated doesn't really improve the logic of the setup in the way I had hoped. Since we have to bind() anyway, there's no reason to first bind and then unbind placeholders, we might as well just bind everything right in place. Here's the non-templated version:

void sendBarToID_15(std::function<void(int)> f)
{
f(15);
}

void yum()
{
// No avoiding this in the presence of overloads
void (Foo::*mfp)(int, int, int) = &Foo::bar;

sendBarToID_15(std::bind(mfp, this, std::placeholder::_1, 17, 29));
}

I was hoping that the variadic template solution could somehow make the client code simpler, but now I don't see how it can get any simpler than this. Variadic #define macros take care of the rest.

Thank you for the contributions!

Update: OK, here's what I finally came up with, thanks to preprocessor macros:

#include <functional>
#include <iostream>

class Baz;

class Foo
{
void bar(int ID, const int &, int)
{ std::cout << "v1 called with ID " << ID << "\n"; }
void bar(int ID)
{ std::cout << "v2 called with ID " << ID << "\n"; }
void bar(int ID, double, float, void(Baz::*)()) const
{ std::cout << "v3 called with ID " << ID << "\n"; }

void innocent(int ID, double)
{ std::cout << "innocent called with ID " << ID << "\n"; }

void very_innocent(int ID, double) const
{ std::cout << "very innocent called with ID " << ID << "\n"; }

template<int ID> void sendBarToID(std::function<void(int)> refB) { refB(ID); }
template<int ID> void sendConstBarToID(std::function<void(int)> refB) const { refB(ID); }

#define MAKE_CALLBACK(f, ...) std::bind(&Foo::f, this, std::placeholders::_1, __VA_ARGS__)
#define MAKE_EXPLICIT_CALLBACK(g, ...) std::bind(g, this, std::placeholders::_1, __VA_ARGS__)

#define MAKE_SIGNED_CALLBACK(h, SIGNATURE, ...) MAKE_EXPLICIT_CALLBACK(static_cast<void (Foo::*)SIGNATURE>(&Foo::h), __VA_ARGS__)
#define MAKE_CONST_SIGNED_CALLBACK(h, SIGNATURE, ...) MAKE_EXPLICIT_CALLBACK(static_cast<void (Foo::*)SIGNATURE const>(&Foo::h), __VA_ARGS__)

public:
void gobble()
{
double q = .5;
int n = 2875;
void(Baz::*why)();

sendBarToID<5>(MAKE_CALLBACK(innocent, q));
sendConstBarToID<7>(MAKE_CALLBACK(very_innocent, q));
// sendBarToID<11>(MAKE_SIGNED_CALLBACK(bar, (int))); // can't do, too much commas
sendBarToID<13>(MAKE_SIGNED_CALLBACK(bar, (int, const int &, int), n, 1729));
sendConstBarToID<17>(MAKE_CONST_SIGNED_CALLBACK(bar, (int, double, float, void(Baz::*)()), q, q, why));
}

void yum() const
{
double q = .5;
int n = 2875;
void(Baz::*why)();

sendConstBarToID<2>(MAKE_CALLBACK(very_innocent, q));
// sendBarToID<-1>(MAKE_CALLBACK(innocent, q)); // Illegal in const function

sendConstBarToID<3>(MAKE_CONST_SIGNED_CALLBACK(bar, (int, double, float, void(Baz::*)()), q, q, why));
}
};

int main()
{
Foo foo;
foo.yum();
foo.gobble();
}

There is one inconvenience: I need to define two separate functions and macros for constant and non-constant member functions. Also, I can't handle the empty argument list (Foo::bar(int)).

Is it possible to return a variadic lambda from a function template?

In C++11, the following works if you’re willing to manually create the function object:

template <typename F>
struct min_on_t {
min_on_t(F f) : f(f) {}

template <typename T, typename... Ts>
auto operator ()(T x, Ts... xs) -> typename std::common_type<T, Ts...>::type
{
// Magic happens here.
return f(x);
}

private: F f;
};

template <typename F>
auto min_on(F f) -> min_on_t<F>
{
return min_on_t<F>{f};
}

And then call it:

auto minimum = min_on(mod7)(2, 8, 17, 5);

To use lambdas in C++14, you need to omit the trailing return type because you cannot specify the type of the lambda without assigning it to a variable first, because a lambda expression cannot occur in an unevaluated context.

template <typename F>
auto min_on(F f)
{
return [f](auto x, auto... xs) {
using rettype = std::common_type_t<decltype(x), decltype(xs)...>;
using f_rettype = decltype(f(x));

rettype result = x;
f_rettype result_trans = f(x);
(void)std::initializer_list<int>{
(f(xs) < result_trans
? (result = static_cast<rettype>(xs), result_trans = f(xs), 0)
: 0)...};
return result;
};
}


Related Topics



Leave a reply



Submit