Specializing a Template on a Lambda in C++0X

Specializing a template on a lambda in C++0x

I think it is possible to specialize traits for lambdas and do pattern matching on the signature of the unnamed functor. Here is the code that works on g++ 4.5. Although it works, the pattern matching on lambda appears to be working contrary to the intuition. I've comments inline.

struct X
{
float operator () (float i) { return i*2; }
// If the following is enabled, program fails to compile
// mostly because of ambiguity reasons.
//double operator () (float i, double d) { return d*f; }
};

template <typename T>
struct function_traits // matches when T=X or T=lambda
// As expected, lambda creates a "unique, unnamed, non-union class type"
// so it matches here
{
// Here is what you are looking for. The type of the member operator()
// of the lambda is taken and mapped again on function_traits.
typedef typename function_traits<decltype(&T::operator())>::return_type return_type;
};

// matches for X::operator() but not of lambda::operator()
template <typename R, typename C, typename... A>
struct function_traits<R (C::*)(A...)>
{
typedef R return_type;
};

// I initially thought the above defined member function specialization of
// the trait will match lambdas::operator() because a lambda is a functor.
// It does not, however. Instead, it matches the one below.
// I wonder why? implementation defined?
template <typename R, typename... A>
struct function_traits<R (*)(A...)> // matches for lambda::operator()
{
typedef R return_type;
};

template <typename F>
typename function_traits<F>::return_type
foo(F f)
{
return f(10);
}

template <typename F>
typename function_traits<F>::return_type
bar(F f)
{
return f(5.0f, 100, 0.34);
}

int f(int x) { return x + x; }

int main(void)
{
foo(f);
foo(X());
bar([](float f, int l, double d){ return f+l+d; });
}

Template specialization for passing a lambda

you cannot do this directly: the type of the lambda is created by the compiler and is different for each lambda. You can specialize for it, but it would be for that type only (see example below). You can remove some of the tediousness though by using a small function for converting lambda -> std::function.

auto myLambda = [](){ std::cout << "myLambda" << std::endl; };

class X
{
public:
template <typename T>
void f( T t )
{
std::cout << "not so awesome" << std::endl;
};

void f( const std::function< void() >& f )
{
std::cout << "function" << std::endl;
}

void f( const decltype( myLambda )& f )
{
std::cout << "myLambda" << std::endl;
}
};

//helper for lambda -> function
template< class T >
std::function< void() > Function( const T& f )
{
return std::function< void() >( f );
}

X x;
x.f( myLambda ); //prints "myLambda"
x.f( Function( [](){ std::cout << "blah" << std::endl; } ) ); //prints "function"
x.f( [](){ std::cout << "blah" << std::endl; } ); //still won't work: not the same type as myLambda!

Lambda expressions as class template parameters

As of C++20, this answer is now outdated. C++20 introduces stateless lambdas in unevaluated contexts1:

This restriction was originally designed to prevent lambdas from appearing in signatures, which would have opened a can of worm for mangling because lambdas are required to have unique types. However, the restriction is much stronger than it needs to be, and it is indeed possible to achieve the same effect without it

Some restrictions are still in place (e.g. lambdas still can't appear on function signatures), but the described usecase is now completely valid and the declaration of a variable is no longer necessary.



I'm asking if you can do something like:

Foo<decltype([]()->void { })> foo;

No you can't, because lambda expressions shall not appear in an unevaluated context (such as decltype and sizeof, amongst others).
C++0x FDIS, 5.1.2 [expr.prim.lambda] p2

The evaluation of a lambda-expression results in a prvalue temporary (12.2). This temporary is called the
closure object. A lambda-expression shall not appear in an unevaluated operand (Clause 5). [ Note: A
closure object behaves like a function object (20.8).—end note ]
(emphasis mine)

You would need to first create a specific lambda and then use decltype on that:

auto my_comp = [](const std::string& left, const std::string& right) -> bool {
// whatever
}

typedef std::unordered_map<
std::string,
std::string,
std::hash<std::string>,
decltype(my_comp)
> map_type;

That is because each lambda-derived closure object could have a completely different type, they're like anonymous functions after all.

Using a lambda in place of an index-able template parameter

template<class F>
struct square_bracket_invoke_t {
F f;
template<class T>
auto operator[](T&& t)const
-> typename std::result_of< F const&(T&&) >::type
{ return f(std::forward<T>(t)); }
};
template<class F>
square_bracket_invoke_t< typename std::decay<F>::type >
make_square_bracket_invoke( F&& f ) {
return {std::forward<F>(f)};
}

Live example.

Code is C++11 and has basically zero overhead.

int main() {
std::cout << foo( 6, make_square_bracket_invoke([](int x){ return x; } ) ) << "\n";
}

result is 0+1+2+3+4+5 aka 15.

Is this a good idea? Maybe. But why stop there?

For max amusement:

const auto idx_is = make_square_bracket_invoke([](auto&&f){return make_square_bracket_invoke(decltype(f)(f));});
int main() {
std::cout << foo( 6, idx_is[[](int x){ return x; }] ) << "\n";
}

Compilation error when using class template parameter in lambda

I think you still have the 0x syntax slightly messed up.

auto myFunc = [&] ( returnType (*functionPointerToAdd)(T1) ) -> returnType (*)(T1, T2, T3) { functionPointerToAdd(T1); };

This line is the problem. Let's pull it apart:

auto myFunc = [&]

This creates a local variable of undefined type that receives a lambda (which is fine) and starts a lambda, that takes the function pointer by reference. The pointer is going to go away, better make it a [=] for simplicity (you'll copy a full pointer more - no biggie).

( returnType (*functionPointerToAdd)(T1) ) 

This is the arguments to the lambda. This means that the lambda should be called (!) with a function pointer of the type returnType(*)(T1). That's not what you want - you want it to be called with T1, T2, T3.

-> returnType (*)(T1, T2, T3)

This defines the return type of the lambda - and only that. You're now saying that it has to return a function pointer, to a function returning a returnType and taking a T1, T2 and T3 as argument. The lambda type would then be

(returnType(*)(T1, T2, T3)) (*)(returnType(*)(T1))

, or a function that takes a function pointer as argument and that returns a function pointer. Yes, it's overly complex and unreadable.

{ functionPointerToAdd(T1); };

This finally is the contents of the lambda and the reason for your error. You're passing the type T1 as argument to the function pointer.

Suggestion for fixing it (as the syntax is horribly complex - I hope the above text will help you with why I changed what:

auto myFunc = [=] ( T1 arg, T2, T3 ) -> returnType { return functionPointerToAdd(arg); }

Feel free to ask why & how.

How to extract lambda's Return Type and Variadic Parameters Pack back from general templatetypename T

template <typename T>
struct function_traits : public function_traits<decltype(&T::operator())>
{};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
// we specialize for pointers to member function
{
using result_type = ReturnType;
using arg_tuple = std::tuple<Args...>;
static constexpr auto arity = sizeof...(Args);
};

template <class F, std::size_t ... Is, class T>
auto lambda_to_func_impl(F f, std::index_sequence<Is...>, T) {
return std::function<typename T::result_type(std::tuple_element_t<Is, typename T::arg_tuple>...)>(f);
}

template <class F>
auto lambda_to_func(F f) {
using traits = function_traits<F>;
return lambda_to_func_impl(f, std::make_index_sequence<traits::arity>{}, traits{});
}

The code above should do what you want. The main idea, as you can see, is to create an integer pack. This is the non-type template equivalent of variadics. I don't know of any technique by which you can use such a pack without calling another function, so typically in these situations with tuples you'll see a nested "impl" function that does all the work. Once you have the integer pack, you expand it while accessing the tuple (works for getting the values too).

On a stylistic note: use using, not typename, especially in template heavy code as the former can alias templates too. And don't use that enum trick to store a static value without it using space; compilers will optimize this out anyhow and just using a static constexpr integer is much clearer.

Using variadic template arguments to resolve a lambda signature

Summing up and extending from the comments:

Per [expr.prim.lambda]/3, the type of a lambda-expression is a class type, just like "ordinary, named function object types":

The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type — called the closure type [...]

Further down, /5 specifies:

The closure type for a lambda-expression has a public inline function call operator (13.5.4) whose parameters and return type are described by the lambda-expression’s parameter-declaration-clause and trailing-return-type respectively. This function call operator is declared const (9.3.1) if and only if the lambda-expression’s parameter-declaration-clause is not followed by mutable. It is neither virtual nor declared
volatile. [...]

(it then continues by specifying attributes and exception-specifications)

Which means that the lambda [](int p){ return p/2.0; } behaves in this regard exactly like

struct named_function_object
{
double operator() (int p) const { return p/2.0; }
};

Therefore, your first specialization

template<typename R, typename...As>
struct invokable_type<R(As...)>;

should already be able to deal with lambdas. The SSCCE

#include <utility>

template<class T>
struct decompose;

template<class Ret, class T, class... Args>
struct decompose<Ret(T::*)(Args...) const>
{
constexpr static int n = sizeof...(Args);
};

template<class T>
int deduce(T t)
{
return decompose<decltype(&T::operator())>::n;
}

struct test
{
void operator() (int) const {}
};

#include <iostream>
int main()
{
std::cout << deduce(test{}) << std::endl;
std::cout << deduce([](int){}) << std::endl;
}

compiles fine on recent versions of clang++ and g++. It seems the problem is related to g++4.7


Further research shows that g++-4.7.3 compiles the above example.

The problem might be related to the misconception that a lambda-expression would yield a function type. If we define do_something as

template<class C>
void do_something(C&&)
{
std::cout << invokable_type<C>::n << std::endl;
}

Then for a call like do_something( [](int){} ), the template parameter C will be deduced to the closure type (no reference), i.e. a class type. The analogous case for the struct test defined above, would be do_something( test{} ), in which case C would be deduced to test.

The specialization of invokable_type that is instantiated is therefore the general case

template<class T>
struct invokable_type;

as T in both cases is not a "composite type" like a pointer or function type. This general case can be used by assuming it only takes a pure class type, and then using the member T::operator() of that class type:

template<class T>
struct invokable_type
{
constexpr static int n = invokable_type<&T::operator()>::n;
};

or, as Potatoswatter put it, via inheritance

template<class T>
struct invokable_type
: invokable_type<&T::operator()>
{};

Potatoswatter's version however is more general and probably better, relying on a SFINAE check for the existance of T::operator(), which can provide a better diagnostic message if the operator cannot be found.

N.B. If you prefix a lambda-expression that doesn't capture anything with a unary +, it'll be converted to a pointer-to-function. do_something( +[](int){} ) will work with a specialization invokable_type<Return(*)(Args...)>.

How to use a lambda expression as a template parameter?

The 2nd template parameter of std::set expects a type, not an expression, so it is just you are using it wrongly.

You could create the set like this:

auto comp = [](const A& lhs, const A& rhs) -> bool { return lhs.x < rhs.x; };
auto SetOfA = std::set <A, decltype(comp)> (comp);


Related Topics



Leave a reply



Submit