Generic lambdas were introduced in C++14
.
Simply, the closure type defined by the lambda expression will have a templated call operator rather than the regular, non-template call operator of C++11
's lambdas (of course, when auto
appears at least once in the parameter list).
So your example:
auto glambda = [] (auto a) { return a; };
Will make glambda
an instance of this type:
class /* unnamed */
{
public:
template<typename T>
T operator () (T a) const { return a; }
};
Paragraph 5.1.2/5 of the C++14 Standard Draft n3690 specifies how the call operator of the closure type of a given lambda expression is defined:
The closure type for a non-generic 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. For a generic lambda, the closure type has a public inline function call
operator member template (14.5.2) whose template-parameter-list consists of one invented type template-parameter
for each occurrence of auto in the lambda’s parameter-declaration-clause, in order of appearance.
The invented type template-parameter is a parameter pack if the corresponding parameter-declaration declares
a function parameter pack (8.3.5). The return type and function parameters of the function call
operator template are derived from the lambda-expression’s trailing-return-type and parameter-declarationclause
by replacing each occurrence of auto in the decl-specifiers of the parameter-declaration-clause with
the name of the corresponding invented template-parameter.
Finally:
Is it similar to templates where for each different argument type compiler generates functions with the same body but changed types or is it more similar to Java's generics?
As the above paragraph explains, generic lambdas are just syntactic sugar for unique, unnamed functors with a templated call operator. That should answer your question :)
This technique will work in some cases. I create a fake_anything
type that can fake almost anything, and try to invoke your lambda with some number of instances of that.
#include <iostream>
struct fake_anything {
fake_anything(fake_anything const&);
fake_anything();
fake_anything&operator=(fake_anything const&);
template<class T>operator T&() const;
template<class T>operator T&&() const;
template<class T>operator T const&() const;
template<class T>operator T const&&() const;
fake_anything operator*() const;
fake_anything operator++() const;
fake_anything operator++(int) const;
fake_anything operator->() const;
template<class T>fake_anything(T&&);
};
fake_anything operator+(fake_anything, fake_anything);
fake_anything operator-(fake_anything, fake_anything);
fake_anything operator*(fake_anything, fake_anything);
fake_anything operator/(fake_anything, fake_anything);
// etc for every operator
template<class>using void_t=void;
template<class Sig, class=void>
struct can_invoke:std::false_type{};
template<class F, class...Args>
struct can_invoke<F(Args...),
void_t< decltype( std::declval<F>()( std::declval<Args>()... ) ) >
> : std::true_type
{};
template<class Sig>struct is_sig:std::false_type{};
template<class R, class...Args>struct is_sig<R(Args...)>:std::true_type{};
template<unsigned...>struct indexes{using type=indexes;};
template<unsigned Max,unsigned...Is>struct make_indexes:make_indexes<Max-1,Max-1,Is...>{};
template<unsigned...Is>struct make_indexes<0,Is...>:indexes<Is...>{};
template<unsigned max>using make_indexes_t=typename make_indexes<max>::type;
template<class T,unsigned>using unpacker=T;
template<class F, class A, class indexes>
struct nary_help;
template<class F, class A, unsigned...Is>
struct nary_help<F,A,indexes<Is...>>:
can_invoke<F( unpacker<A,Is>... )>
{};
template<class F, unsigned N>
struct has_n_arity:
nary_help<F, fake_anything, make_indexes_t<N>>
{};
template<class F, unsigned Min=0, unsigned Max=10>
struct max_arity{
enum{Mid=(Max+Min)/2};
enum{
lhs = max_arity<F,Min,Mid>::value,
rhs = max_arity<F,Mid+1,Max>::value,
value = lhs>rhs?lhs:rhs,
};
};
template<class F, unsigned X>
struct max_arity<F,X,X>:
std::integral_constant<int, has_n_arity<F,X>::value?(int)X:-1>
{};
template<class F, unsigned Min=0, unsigned Max=10>
struct min_arity{
enum{Mid=(Max+Min)/2};
enum{
lhs = min_arity<F,Min,Mid>::value,
rhs = min_arity<F,Mid+1,Max>::value,
value = lhs<rhs?lhs:rhs,
};
};
template<class F, unsigned X>
struct min_arity<F,X,X>:
std::integral_constant<unsigned,has_n_arity<F,X>::value?X:(unsigned)-1>
{};
auto test1 = [](auto x, auto y)->bool { return x < y; };
auto test2 = [](auto x, auto y) { return x + y; };
auto test3 = [](auto x) { return x.y; };
int main() {
std::cout << can_invoke< decltype(test1)( fake_anything, fake_anything ) >::value << "\n";
std::cout << can_invoke< decltype(test1)( int, int ) >::value << "\n";
std::cout << has_n_arity< decltype(test1), 2 >::value << "\n";
std::cout << max_arity< decltype(test1) >::value << "\n";
std::cout << max_arity< decltype(test2) >::value << "\n";
// will fail to compile:
// std::cout << max_arity< decltype(test3) >::value << "\n";
}
live example.
Note sufficient SFINAE will mean the above will get the wrong result, as will use of operator.
, or use of operator.
on certain kinds of "derived" types, or accessing types based off of the fake_anything
parameter, etc.
However, if the lambda specifies its return value with a ->X
clause, then fake_anything
is more than good enough. The hard part is dealing with the body.
Note that this approach is often a bad idea, because if you want to know the arity of a function, you probably also know the types of the things you want to invoke the function object with! And above I answer that question really easily (can this function object be invoked with these arguments?). It can even be improved to ask "what is the longest/shortest prefix of these arguments that can invoke this function object", or handle "how many repeats of type X work to invoke this function object" (if you want clean failure, you need an upper bound).
Best Answer
From [expr.const]:
In
f(x)
, we do an lvalue-to-rvalue conversion onx
.x
isn't of integral or enumeration type, it's not a subobject of a string-literal, it's not an object defined with constexpr, and its lifetime did not begin with the evaluation off(x)
.That seems to make this not a core constant expression.
However, as Casey points out, since
S
is empty, nothing in its implicitly-generated copy constructor would actually trigger this lvalue-to-rvalue conversion. That would mean that nothing in this expression actually violates any of the core constant expression restrictions, and hence gcc and clang are correct in accepting it. This interpretation seems correct to me.constexpr
is fun.