Will I Be Able to Declare a Constexpr Lambda Inside a Template Parameter

Will I be able to declare a constexpr lambda inside a template parameter?

No, that is a compiler bug. gcc 7.1 correctly rejects the code.

[expr.prim.lambda]/2:

A lambda-expression is a prvalue whose result object is called the closure object. A lambda-expression shall not appear in an unevaluated operand, in a template-argument, in an alias-declaration, in a typedef declaration, or in the declaration of a function or function template outside its function body and default arguments.

As you can see from the part that I marked as bold, a lambda expression cannot appear in a template argument list.

This is also made clear in a subsequent note:

[ Note: The intention is to prevent lambdas from appearing in a signature. — end note ]

If I were to guess, I would say that the bug comes about because starting with C++17, lambdas are implicitly constexpr, which makes them valid to be called in compile time expressions, like template arguments. But actually defining a lambda in a template argument is still illegal.


Note that this restriction has been lifted in C++20. :)

Can a lambda instantiate a template function?

The answer to your question is technically yes, lambda bodies can instantiate template functions. The actual example doesn't work, because int n as a parameter can't be used that way.

There is an easy workaround

template<auto x>
using constant_t = std::integral_constant< std::decay_t<decltype(x)>, x >;
template<auto x>
constexpr constant_t<x> constant = {};

template <int n> int fn () { int arr[n] = {0}; return sizeof(arr); }
auto fx = [] (auto n) { return fn<n>(); };
std::cout << fx( constant<3> );

Live example.

Here I made the constant<x> variable template that creates an instance of std::integral_constant<X, x>. This is a stateless (but not valueless!) type that has a constexpr conversion to its value.

We can pass that to a lambda, and so long as the lambda takes it by value we can then convert it to a constexpr value within the lambda, including passing it as a template non-type parameter, instantiating a template function specialization as you are asking for.

The can be done without the constant variable template, ie if you don't have auto parameter support:

template<std::size_t N>
using index_t = std::integral_constant<std::size_t, N>;
template<std::size_t N>
constexpr index_t<N> index = {};

we can use a type-specific version of it, and just pass that, and it works the same way.


Aside, constant<?> is fun. For example:

using upFILE=std::unique_ptr<
std::FILE,
constant_t<std::fclose>
>;

upFILE file( fopen("hello.txt", "r") );

does the right thingtm.

Constexpr lambda argument

In this:

ForEach([&t](size_t index)
{
std::cout << std::get<index>(t) << ' ' << std::endl;
});

index is not a constant expression. It's just a variable. Function parameters are not constexpr.

But if we tweaked ForEach somewhat (to work the same way as the example of mine that you linked):

template <class Func, std::size_t... index>
inline constexpr void ForEach(Func && f, std::index_sequence<index...>)
{
(f(std::integral_constant<std::size_t, index>()), ...);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// instead of just index
}

ForEach([&t](auto index)
{
std::cout << std::get<index>(t) << ' ' << std::endl;
});

Then this works because index is no longer size_t but rather different instances of std::integral_constant<size_t, V> for various V. That type looks something like:

template<class T, T v>
struct integral_constant {
static constexpr T value = v;
typedef T value_type;
typedef integral_constant type; // using injected-class-name
constexpr operator value_type() const noexcept { return value; }
constexpr value_type operator()() const noexcept { return value; } //since c++14
};

Converting a std::integral_constant<size_t, V> to a size_t invokes the constepxr operator size_t(), which doesn't involve reading any state from this object itself (which is an empty type), hence it's allowed as a constant expression.

A different way of looking at it is that we're encoding the value in the type (which can be retrieved as a constant expression) rather than in the value (which cannot).

Evaluated constexpr lambda in non-type template argument

It is quite intentional that lambdas do not appear in unevaluated contexts. The fact that lambdas always have unique types leads to all sorts of issues.

Here are a few examples from a comp.lang.c++ discussion, from Daniel Krugler:

There would indeed exist a huge number of use-cases for allowing lambda
expressions, it would probably extremely extend possible sfinae cases
(to include complete code "sand-boxes"). The reason why they became
excluded was due to exactly this extreme extension of sfinae cases (you
were opening a Pandora box for the compiler) and the fact that it can
lead to problems on other examples as yours, e.g.

template<typename T, typename U>
void g(T, U, decltype([](T x, T y) { return x + y; }) func);

is useless, because every lambda expression generates a unique type, so
something like

g(1, 2, [](int x, int y) { return x + y; });

doesn't actually work, because the type of the lambda used in the
parameter is different from the type of the lambda in the call to g.

Finally it did also cause name-mangling issues. E.g. when you have

template<typename T>
void f(T, A<sizeof([](T x, T y) { return x + y; })> * = 0);

in one translation unit but

template<typename T>
void f(T, A<sizeof([](T x, T y) { return x - y; })> * = 0);

in another translation unit. Assume now that you instantiate f<int>
from both translation units. These two functions have different
signatures, so they must produce differently-mangled template
instantiations. The only way to keep them separate is to mangle the
body of the lambdas. That, in turn, means that compiler writers have
to come up with name mangling rules for every kind of statement in the
language. While technically possible, this was considered as both a
specification and an implementation burden.

That's a whole bundle of problems. Especially given that your motiviation of writing:

int N = S<[]()constexpr{return 42;}()>::value;

can be easily solved by instead writing:

constexpr auto f = []() constexpr { return 42; }
int N = S<f()>::value;

Trying to pass a constexpr lambda and use it to explicitly specify returning type

Parameters to constexpr functions are not themselves constexpr objects - so you cannot use them in constant expressions. Both of your examples returning arrays are ill-formed because there is no valid call to them.

To understand why, consider this nonsense example:

struct Z { int i; constexpr int operator()() const { return i; }; };

template <int V> struct X { };
template <typename F> constexpr auto foo(F f) -> X<f()> { return {}; }

constexpr auto a = foo(Z{2});
constexpr auto b = foo(Z{3});

Z has a constexpr call operator, and this is well-formed:

constexpr auto c = Z{3}();
static_assert(c == 3);

But if the earlier usage were allowed, we'd have two calls to foo<Z> that would have to return different types. This could only fly if the actual value f were the template parameter.


Note that clang compiling the declaration is not, in of itself, a compiler error. This is a class of situations that are ill-formed, no diagnostic required.

Constructing a constexpr lambda with member function pointer

If you want to make next statement constexpr

auto invokable = createLambda(method);

Pass method as non-type template argument

template<auto method>
auto passthrough() // Function cannot be constexpr
{
constexpr auto invokable = createLambda(method); // This is not valid, what are my alternatives to make this possible?
constexpr uint64_t someOtherTask = 1 + 2;
g_map[someOtherTask] = invokable;
return invokable;
}

And the call it like this

auto invoke2 = passthrough<&Data::returnData>();

The reason why you can not do auto invokable = createLambda(method); when method is function argument, because function arguments are not constexpr. Since, function is not scope of creating argument object.

If method was created inside passthrough it would work. Template argument is guaranteed to be evaluted at compile-time that's the reason why it can be used in function in constepxr context even, if was created outside of function

Code snippet in godbolt compiler https://godbolt.org/z/c6aqWW9j6

Can lambdas be used as non-type template parameter?

Can lambdas be used as non-type template parameter?

Yes, with implementations that has implemented P0732R2 - Class types in non-type template parameters but clang++ has not implemented it yet.

Source:
https://en.cppreference.com/w/cpp/compiler_support


Note that the lambda needs to be at least constexpr (which it is by default):

When this specifier is not present, the function call operator will be
constexpr anyway, if it happens to satisfy all constexpr function requirements.

You can however add constexpr to get an error on the lambda itself instead of when using it as a template parameter. As a side note: You can also specify it to be consteval to make it work as a non-type template parameter.

A stateful lambda can be constexpr:

constexpr auto lmb1 = [](int i) {
static int x = 0;
return i*i + ++x;
};

while a lambda capturing by reference, or capturing by copy and mutating (mutable), can not. Capturing by copying a constexpr is ok though.

Generic lambdas may be constexpr too:

constexpr auto gen_lmb = []<typename T>(T& val) {
val += val;
return val;
};

template <auto Lambda>
struct A {
template<typename T>
void doit(T&& arg) {
std::cout << Lambda(arg) << '\n';
}
};

//...

A<gen_lmb> ginst;

int v = 1000;
ginst.doit(v);
ginst.doit(std::string("foo "));
std::cout << v << '\n';
2000
foo foo
2000

Why is this nested lambda not considered constexpr?

The problem is you are trying to odr-use one of a lambda's captured variables in a template non-type argument.

  return hana::type_c<Array<typename decltype(type)::type, size()>>;
// ^~~~

A template non-type argument must be a constant expression. Inside a lambda, you can't odr-use a captured variable in a constant expression. Whether or not the lambda is constexpr is irrelevant.

But you can odr-use ordinary variables in constant expressions, even if they are not constexpr variables. For example, this is legal:

std::integral_constant<int, 100> i; // i is not constexpr
std::array<int, i()> a; // using i in a constant expression

So why can't we odr-use captured variables in constant expressions? I don't know the motivation for this rule, but here it is in the standard:

[expr.const]

(¶2) A conditional-expression is a core constant expression unless...
(¶2.11) in a lambda-expression, a reference to this or to a variable with automatic storage duration defined outside that lambda-expression, where the reference would be an odr-use.

CWG1613 may hold some clue.

If we were to rewrite the inner lambda as a named class, we would have a different but related problem:

template <typename T>
struct Closure {
T size;
constexpr Closure(T size_) : size(size_) {}

template <typename U>
constexpr auto operator()(U type) const {
return hana::type_c<Array<typename decltype(type)::type, size()>>;
}
};
constexpr auto array_ = [] (auto size) {
return Closure { size };
};

Now the error would be the implicit use of the this pointer in a template non-type argument.

  return hana::type_c<Array<typename decltype(type)::type, size()>>;
// ^~~~~

I declared Closure::operator()() as a constexpr function for consistency, but that is immaterial. The this pointer is forbidden to be used in a constant expression ([expr.const] ¶2.1). Functions declared constexpr do not get special dispensation to relax the rules for the constant expressions that may appear within them.

Now the original error makes a little bit more sense, because captured variables are transformed into data members of the lambda's closure type, so using captured variables is a little bit like indirecting through the lambda's own "this pointer".

This is the workaround that introduces the least alteration to the code:

constexpr auto array_ = [] (auto size) {
return [=] (auto type) {
const auto size_ = size;
return hana::type_c<Array<typename decltype(type)::type, size_()>>;
};
};

Now we are using the captured variable outside of a constant expression, to initialize an ordinary variable which we can then use in the template non-type argument.

This answer has been edited a few times, so the comments below may reference previous revisions.

C++ creating lambda from other constexpr lambdas executed in order can't be constexpr

Make funcs_tuple static. Because it is constexpr, this shouldn't change really change anything about how your code functions, since all calls to whatever function this is should always arrive at the same value of funcs_tuple. (The differences would be if you were taking the address of it for some reason, I guess.) However, it does make references to funcs_tuple constexpr, because now there is one object represented by the constexpr variable and not one per invocation of the function. Godbolt

Note that this doesn't work for a constexpr function. Thankfully, if the enclosing function is constexpr, the variables don't need to be. That is, you can do both/either

void func() { // func not constexpr
static constexpr auto funcs_tuple = ...;
constexpr auto combined_funcs = do_funcs(funcs_tuple);
}

or

// like in the question
template <typename... Funcs>
constexpr auto make_do_funcs(Funcs&&... fs) {
// funcs not constexpr or static; make_do_funcs still constexpr
const auto funcs = std::tuple(std::forward<Funcs>(fs)...);
return do_funcs(funcs)(); // or whatever
// note: you CANNOT return do_funcs(funcs), because that would be a dangling reference
// your original make_do_funcs is simply broken
}


Related Topics



Leave a reply



Submit