15

How would I go about testing if a lambda is stateless, that is, if it captures anything or not? My guess would be using overload resolution with a function pointer overload, or template specialization?

int a;
auto l1 = [a](){ return 1; };
auto l2 = [](){ return 2; };
// test l1 and l2, get a bool for statelessness.
Luc Danton
  • 34,649
  • 6
  • 70
  • 114
Skeen
  • 4,614
  • 5
  • 41
  • 67
  • 8
    Hint: if it is convertible to function pointer, it is stateless. – Nawaz Nov 13 '13 at 18:50
  • 1
    @Nawaz: Interesting. Do you have any quote from standard? – masoud Nov 13 '13 at 18:51
  • 2
    @MM.: I don't remember any quote, but if a lambda doesn't capture any variable, then it is implicitly convertible to function pointer. – Nawaz Nov 13 '13 at 18:55
  • @Nawaz: I find something and made an answer, would you check it? – masoud Nov 13 '13 at 18:59
  • Personally I would be interested whether it's also possible to get the size of the capture. – Alexander Oh Nov 13 '13 at 19:07
  • 3
    Personally I wonder why it would matter, and why anyone would want to single out lambdas. My poor old function objects feel neglected. – R. Martinho Fernandes Nov 13 '13 at 19:44
  • 1
    @R.MartinhoFernandes: Because stateless lambda's convert to precisely one function pointer type, and calling the resulting pointer does the same? No guarantees in the general functor case. – MSalters Nov 14 '13 at 01:09
  • @MSalters and yet they're indistinguishable from any other object with such a conversion. – R. Martinho Fernandes Nov 14 '13 at 09:28
  • Before blindly converting stateless lambdas to function pointers, recall that: (1) Calls to lambdas are more easily inlined than calls through function pointers. (2) The size of a stateless lambda is **likely** to be one byte whereas a function pointer is much bigger (typically 4 or 8 bytes). – Cassio Neri Nov 14 '13 at 16:22

5 Answers5

15

As per the Standard, if a lambda doesn't capture any variable, then it is implicitly convertible to function pointer.

Based on that, I came up with is_stateless<> meta-function which tells you whether a lambda is stateless or not.

#include <type_traits>

template <typename T, typename U>
struct helper : helper<T, decltype(&U::operator())>
{};

template <typename T, typename C, typename R, typename... A>
struct helper<T, R(C::*)(A...) const> 
{
    static const bool value = std::is_convertible<T, R(*)(A...)>::value;
};

template<typename T>
struct is_stateless
{
    static const bool value = helper<T,T>::value;
};

And here is the test code:

int main() 
{
    int a;
    auto l1 = [a](){ return 1; };
    auto l2 = [](){ return 2; };
    auto l3 = [&a](){ return 2; };

    std::cout<<std::boolalpha<<is_stateless<decltype(l1)>::value<< "\n";
    std::cout<<std::boolalpha<<is_stateless<decltype(l2)>::value<< "\n";
    std::cout<<std::boolalpha<<is_stateless<decltype(l3)>::value<< "\n";
}

Output:

false
true
false

Online Demo.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • Can you explain `&U::operator()` - does that require the lambda call itself to take zero args? – Aaron McDaid Nov 13 '13 at 19:30
  • 1
    @AaronMcDaid: No. The lambda can take any arguments. `&U::operator()` takes the pointer of the member function which is defined as `R operator()(Args...)`. So do you see `operator()` there? Actually, `()` is operator, just like `+` in `operator+`. Did that help you? – Nawaz Nov 13 '13 at 19:34
  • @Nawaz, I get it now. A function call operator with zero args would be `operator()()`. The second `()` would contain the arguments, but the initial `()` is simply the name of the operator. To many `(` and `)` can be confusing :P – Aaron McDaid Nov 13 '13 at 19:47
  • 7
    Just note how there's an important test missing here: namely, `is_lambda` (consider: `struct poor_bastard { void operator()() const; operator void(*)(void*)() const; int haha_i_have_state_shut_up; };`). Also note how this will not have any chance of working with the upcoming polymorphic lambdas. – R. Martinho Fernandes Nov 13 '13 at 19:47
  • 3
    @Skeen: Why? Is this particular piece of information of general interest? (no!) Will anything be gained by putting it in the standard vs. implementing it as above on the (fairly rare) occasion that it's needed? (again, no!) I think Nawaz has done a fine job of meeting the OP's request, but I see no gain from putting something similar in the standard. – Jerry Coffin Nov 13 '13 at 19:51
  • 3
    IMO a test that generates both false positives and false negatives is worthless. – R. Martinho Fernandes Nov 13 '13 at 19:53
  • 1
    @R.MartinhoFernandes: I agree. But I don't think something like `is_lambda` can be implemented (`is_stateless` just assumes that you pass a lambda, i.e compiler generated functor). Also, C++11 things cannot support C++14 or C++-future; so it is not correct to demand that. :-) – Nawaz Nov 13 '13 at 19:53
  • 2
    @Nawaz someone mentioned adding it to the standard in the future (and polymorphic function objects without special syntax are not really new) – R. Martinho Fernandes Nov 13 '13 at 19:54
  • @Nawaz: You can support C++-future by not singling out lambdas as if they're something special. There are perfectly fine polymorphic function objects today, just manually specified - or created by something like `std::bind`. – Xeo Nov 13 '13 at 19:55
  • 2
    There is no answer to post, because the whole premise of the question is flawed - ***lambdas are not special, don't do special things just for them***. – Xeo Nov 13 '13 at 19:57
  • @Xeo: Are stateless function objects implicitly castable to function pointers the way lambdas are? - Because otherwise they are actually a bit special, and if function objects are implicitly castable, then the `is_stateless` solution actually applies to function objects as well. – Skeen Nov 13 '13 at 20:01
  • 7
    A lambda without captures is not necessarily stateless btw, making the result of this test even more worthless than it already is. (consider: `static int where_is_your_statelessness_now = 0; void bar() { auto f = [/* look ma, no captures! */]{ return ++where_is_your_statelessness_now; }; ... }`) – R. Martinho Fernandes Nov 13 '13 at 20:04
  • @R.MartinhoFernandes: While it may not be stateless in that sense, it is in fact still bindable to a function pointer, right? – Skeen Nov 13 '13 at 20:12
  • 1
    I don't think the goal of the question was to distinguish 'no-capture lambdas' from 'everything else' - I think it was 'no-capture lambdas' from 'capture lambdas'. I think I agree with @Xeo that we probably shouldn't care about the difference, but this is the question that was asked. – Aaron McDaid Nov 13 '13 at 20:12
  • @Skeen, perhaps you should change 'stateless' to 'no-capture' in your question? – Aaron McDaid Nov 13 '13 at 20:13
  • 2
    @R.MartinhoFernandes: I worked on a *specific* meaning of stateless-ness. The first line of question says, *"How would I go about testing if a lambda is stateless, **that is, if it captures anything or not**? "*. Did you ignore that? – Nawaz Nov 13 '13 at 20:14
  • 1
    @Nawaz No. I'm merely stating that that meaning is a wild goose. – R. Martinho Fernandes Nov 13 '13 at 20:16
  • 1
    I only posted one comment to state that. All the others state something else. (IOW there are so many things wrong with this "problem") – R. Martinho Fernandes Nov 13 '13 at 20:19
  • 5
    Just because a lambda is stateless, does not mean it has a conversion to pointer to function - consider auto L = []() { return [=] { return 1; }; }; auto stateless = L(); But stateless will not have a conversion to fptr. Only lambdas that do not have any explicit, default or init-captures can have conversion to fptr. – Faisal Vali Nov 13 '13 at 21:18
  • Yeah it's more like "if a lambda _cannot_ capture a variable", rather than "doesn't". – Lightness Races in Orbit Nov 14 '13 at 08:26
  • How about `sizeof`? It may not be standard, but the stateless lambdas above seem to have a size of 1 on GCC. – user2023370 Jun 11 '14 at 12:02
8
#include <type_traits> // std::true_type, std::false_type
#include <utility>     // std::declval

template<typename Lambda>
auto is_captureless_lambda_tester(int)
-> decltype( +std::declval<Lambda>(), void(), std::true_type {} );

template<typename Lambda>
auto is_captureless_lambda_tester(long)
-> std::false_type;

template<typename Lambda>
using is_captureless_lambda = decltype( is_captureless_lambda_tester<Lambda>(0) );

Does not work for polymorphic lambdas, require as a precondition that the argument be a closure type. (E.g. is_captureless_lambda<int> is std::true_type.)

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
6

Per § 5.1.2/6

The closure type for a non-generic lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function with C ++ language linkage (7.5) having the same parameter and return types as the closure type’s function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type’s function call operator. For a generic lambda with no lambda-capture, the closure type has a public non-virtual non-explicit const conversion function template to pointer to function.

If it's convertible to a pointer to function, then MAYBE it has to not capture anything (stateless). In action:

int v = 1;
auto lambda1 = [ ]()->void {};
auto lambda2 = [v]()->void {};

using ftype = void(*)();

ftype x = lambda1; // OK
ftype y = lambda2; // Error

You can also use std::is_convertible:

static_assert(is_convertible<decltype(lambda1), ftype>::value, "no capture");
static_assert(is_convertible<decltype(lambda2), ftype>::value, "by capture");
masoud
  • 55,379
  • 16
  • 141
  • 208
  • 1
    Maybe mention using `std::is_convertible` to actually perform the check. – bstamour Nov 13 '13 at 19:00
  • Does the standard require that capturing lambdas must *not* convert to function pointers? Perhaps a capturing lambda could convert to a function pointer that takes an extra pointer (like 'this') that points to the state of the object? – Aaron McDaid Nov 13 '13 at 19:18
  • @MM.: Note that your solution is not generic. You're assuming the signature of the function pointer. If the signature doesn't match, it doesn't necessarily mean lambda is not stateless, it could as well mean that you've used the wrong signature to begin with. – Nawaz Nov 13 '13 at 19:19
  • 1
    Also just because a lambda does not capture anything, does not mean it has a conversion to function pointer. If you write a default-capture and even if the lambda does not actually capture anything - it will not have a conversion-to-fptr. – Faisal Vali Nov 13 '13 at 21:56
0

Boost.TypeTraits is_stateless seems to do the job for whatever reason without much drama:

#include<boost/type_traits.hpp>
#include<cassert>
int main(){
  auto l1 = [a](){ return 1; };
  auto l2 = [](){ return 2; };
  auto l3 = [&a](){ return 2; };

  assert( boost::is_stateless<decltype(l1)>::value == false );
  assert( boost::is_stateless<decltype(l2)>::value == true );
  assert( boost::is_stateless<decltype(l3)>::value == false );
}

boost::is_stateless is simple the combination of other conditions, it can be expressed in terms of standard type traits I suppose:

::boost::is_stateless = 
::boost::has_trivial_constructor<T>::value
&& ::boost::has_trivial_copy<T>::value
&& ::boost::has_trivial_destructor<T>::value
&& ::boost::is_class<T>::value
&& ::boost::is_empty<T>::value

http://www.boost.org/doc/libs/1_60_0/libs/type_traits/doc/html/boost_typetraits/reference/is_stateless.html

Check my other answer based on sizeof: https://stackoverflow.com/a/34873353/225186

Community
  • 1
  • 1
alfC
  • 14,261
  • 4
  • 67
  • 118
0

An option could be to explicitly look a the size of the type, a stateless should in principle have the same size as other stateless types (I picked std::true_type for a reference type).

#include<cassert>
#include<type_traits>

template<class T>
struct is_stateless_lambda : std::integral_constant<bool, sizeof(T) == sizeof(std::true_type)>{};

int main(){

  auto l1 = [a](){ return 1; };
  auto l2 = [](){ return 2; };
  auto l3 = [&a](){ return 2; };

  assert( boost::is_stateless_lambda<decltype(l1)>::value == false );
  assert( boost::is_stateless_lambda<decltype(l2)>::value == true );
  assert( boost::is_stateless_lambda<decltype(l3)>::value == false );
}

I don't know how portable this solution is. In any case check my other solution: https://stackoverflow.com/a/34873139/225186

Community
  • 1
  • 1
alfC
  • 14,261
  • 4
  • 67
  • 118