10

Does C++20 allow a non-capturing lambda decayed to a function pointer to be passed directly as a non-type template parameter? If so, what is the correct syntax?

I have tried the following code in various versions of clang and gcc using -std=c++2a.

#include <iostream>

template<auto f>
struct S {
    static void invoke(int x) { f(x); }
};

using X = S<+[](int x) -> void { std::cout << x << " hello\n"; }>;

int main()
{
    X::invoke(42);
}

gcc compiles the code without complaint and the code runs as expected.

clang fails compilation with the following error:

error: a lambda expression cannot appear in this context
using X = S<+[](int x) -> void { std::cout << x << " hello\n"; }>;
             ^

Here is the full code (online versions):

Clang 10.0.0 HEAD: https://wandbox.org/permlink/n5eKQ4kQqSpDpr4k

Gcc 10.0.0 HEAD 20200113: https://wandbox.org/permlink/vJ44sdMtwCKAFU64

1
  • Any progress? Still the same complain with the newst apple clang. Commented Nov 8, 2020 at 15:39

3 Answers 3

11

Does C++20 allow a non-capturing lambda decayed to a function pointer to be passed directly as a non-type template parameter?

Yes.

Indeed, you can go one step further - you don't even need to convert the lambda to a function pointer. You can just provide the lambda. This is valid C++20:

using Y = S<[](int x) -> void { std::cout << x << " hello\n"; }>;

Sorry this is no longer considered valid C++20, since CWG 2542, lambdas are not "structural" types (the term for which types are usable as non-type template parameter types).


The rule we have in C++20 is that lambdas are now allowed in unevaluated contexts (P0315). Among many other wording changes there, this paper struck the rule that prevented lambdas from being used in template arguments (C++17's [expr.prim.lambda]/2):

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.

That clause does not exist anymore in C++20.

Removing this restriction allows the lambda to be used as a template argument, and the conversion from captureless lambda to function pointer was already constexpr in C++17. clang simply does not implement this feature yet (using T = decltype([]{}); compiles on gcc, not yet on clang). I wouldn't call this a clang bug yet, it's just a clang not-yet-implemented-feature (lambdas in unevaluated contexts is not yet listed as implemented in the cppreference compiler support page).


C++20 non-type template parameters (P1907) allows even dropping the + because captureless lambdas count as structural types ([temp.param]/7) by way of simply not having any data members at all.

Sign up to request clarification or add additional context in comments.

10 Comments

Where does the Standard say captureless lambdas don't have any data members?
@Danra The standard doesn't say anything about lambdas having members at all.
Doesn't that mean it might have non-public members, making it non-eligible to be passed to a template parameter, depending on the implementation? For context, I got here from stackoverflow.com/questions/62324050/…, where, IIUC, that is what Nicol Bolas is claiming in one of the comments.
@Danra I take back what I said. The standard states that for each entity captured by copy, we get a non-static data member, and it's unspecified whether we get non-static data members for entities captured by reference. While it doesn't say that we don't get any non-static data members for stuff not captured, that would also be a fairly user-hostile implementation. Why would someone do that?
This answer is not up-to-date. With resolution of CWG 2542 captureless lambdas must not be structural types. (As discussed above, it wasn't clearly specified beforehand.)
|
0

If the rules on this haven't changed since C++17 then using a lambda as a template parameter is not allowed for the same reason that using a string literal is not allowed. Every lambda has a different type and every string literal refers to a different object. What changed in C++17 is that closure objects are now constexpr. To use a string literal or a lambda as a template parameter, the object must have external linkage. So this is allowed in C++17.

template <auto>
struct S {};

constexpr const char string[] = "String literal";
constexpr auto lambda = []{};

S<string> a;
S<+lambda> b;

The closure object itself cannot be used as a template parameter (so you can't do S<lambda>) but this might have changed in C++20 with three-way comparisons. The reason the objects must have external linkage is because it kind of breaks templates. S<+[]{}> and S<+[]{}> would be considered different types even though they look the same (similarly with S<"">).

2 Comments

Internal linkage has not been required for since C++11 [temp.arg.nontype]/1.3, so while I'm having trouble myself finding exactly where C++17 would forbid this, I don't think linkage is the explanation here…
Also, note that it is unspecified whether string literals refer to distinct objects or not [lex.string]/16. And linkage is a property of a name [basic.link]. Since string literals don't have names, they cannot have linkage…
-2

Template parameter must be a constexpr variable.

There is a relevant proposal N4487 for lambdas.

I don't know if it made it into C++20.

1 Comment

This was adopted for C++17.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.