How to Use the Result of a C++17 Captureless Lambda Constexpr Conversion Operator as a Function Pointer Template Non-Type Argument

Can I use the result of a C++17 captureless lambda constexpr conversion operator as a function pointer template non-type argument?

This is a gcc bug, filed 83258.

In C++14, we used to have a linkage requirement for non-type template parameters of pointer type. But in C++17 (as a result of N4268), the parameter just needs to be a converted constant expression of the correct type, with a few other restrictions (none of which are relevant here). Once we can construct fp, we should be able to use it as a template parameter.

Does a function pointer need to point to a function with external linkage when used as a template parameter?

func<+[](){}> is ill-formed in C++17 per the exact paragraph you linked to. The non-normative note simply explains the motivation for the normative prohibition. It does not - and cannot - limit it. This restriction has been removed in the current working draft by P0315, so it has a good chance of making C++20.

Pre-C++17, a lambda-expression cannot be evaluated inside constant expressions.

The "linkage" part is a duplicate of Can I use the result of a C++17 captureless lambda constexpr conversion operator as a function pointer template non-type argument?. It's a GCC bug.

Constexpr function pointer as template argument MSVC vs GCC

Before C++17, the standard restricts non-null pointer-to-member template arguments to
"a pointer to member expressed as described in [expr.unary.op]". In other words, such an argument must be expressed in the exact form &A::B.

C++17 relaxes this constraint to permit general constant expressions. It appears that this has not yet been implemented in GCC.

Passing capturing lambda as function pointer

A lambda can only be converted to a function pointer if it does not capture, from the draft C++11 standard section 5.1.2 [expr.prim.lambda] says (emphasis mine):

The closure type for a lambda-expression with no lambda-capture has a
public non-virtual non-explicit const conversion function to pointer
to function
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.

Note, cppreference also covers this in their section on Lambda functions.

So the following alternatives would work:

typedef bool(*DecisionFn)(int);

Decide greaterThanThree{ []( int x ){ return x > 3; } };

and so would this:

typedef bool(*DecisionFn)();

Decide greaterThanThree{ [](){ return true ; } };

and as 5gon12eder points out, you can also use std::function, but note that std::function is heavy weight, so it is not a cost-less trade-off.

Why is gcc failing when using lambda for non-type template parameter?

According to http://en.cppreference.com/w/cpp/language/template_parameters#Non-type_template_parameter, it seems like external linkage is no longer a requirement since C++17. The same language is found in the C++17 draft under [temp.arg.nontype] at http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf (note that it is incorrectly linked as a C++14 draft).

The template argument that can be used with a non-type template parameter can be any converted constant expression of the type of the template parameter...

The only exceptions are that non-type template parameters of reference and pointer type cannot refer to/be the address of

  • a subobject (including non-static class member, base subobject, or array
    element);
  • a temporary object (including one created during reference initialization);
  • a string literal;
  • the result of typeid;
  • or the predefined variable __func__.

That link on cppreference also specifically mentions function pointers, pre C++ 17:

The following limitations apply when instantiating templates that have non-type template parameters:

...

For pointers to functions, the valid arguments are pointers to functions with linkage (or constant expressions that evaluate to null pointer values).

Since your question is labelled C++1z (we should probably have a 17 tag by now and use that instead since 17 is finished) we should use the first set of rules. Your example does not seem to fall into any of the exception categories for C++ 17, and therefore gcc is in error.

Note that clang does not compile your example if you change the language flag to 14.

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;

Why can't lambda, when cast to function pointer, be used in constexpr context?

Clang hasn't implemented constexpr lambdas yet.

GCC is behind in other ways. [temp.arg.nontype]/2's only interesting constraint is that the argument shall be a constant expression. But [expr.const]/(5.2) makes it one, so that's perfectly valid. Perhaps GCC didn't implement N4198 yet, which eliminated the linkage requirement.

Note that both constexpr lambdas and no-linkage function pointer template arguments are post C++14 features.

How do I write a lambda expression that looks like a method?

user0042's answer seems the way to go but, just for completeness sake, it's worth mentioning that in C++17 captureless lambdas have a constexpr conversion operator to their function pointer type, hence you should(*) be able of converting such a lambda to a member function pointer, via something like:

// ...
void associateMethod(int index, MyMethod m);

template<typename F>
void associateMethod(int index, F m) {
associateMethod( index,
static_cast<MyMethod>(
&MyClass::bindFun< static_cast<void(*)(MyClass*,int)>(m) >
) );
}

private:

template<auto F>
void bindFun(int x){ (*F)(this,x); }

// to be used like
x.associateMethod(0,[](MyClass* this_, int x){ this_->method1(x+1); });

(*) sadly, this compiles in clang but gcc refuses to compile it (I'm going to ask a question about this, you can find it here).



Related Topics



Leave a reply



Submit