Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

lambda as a static member

I'm trying to use a lambda as a static member, like this:

struct A {     static constexpr auto F = [](){}; };   int main() {     A::F();     return 0; } 

Is this even correct C++11 code? On clang, I get this error:

error: constexpr variable 'F' must be initialized by a constant       expression     static constexpr auto F = [](){};                               ^~~~~~ 

It seems in clang, lambdas aren't considered a constant expression. Is this correct? Perhaps they haven't fully implemented lambdas yet in clang because gcc 4.7 seems to allow it as a constexpr, but it give another error:

error: ‘constexpr const<lambda()> A::F’, declared using local type ‘const<lambda()>’, is used but never defined 

I'm not sure, I understand what that means. It seems to correctly deduce the type of the lambda, but it only declares it and not define it. How would I go about defining it?

like image 592
Paul Fultz II Avatar asked Jul 30 '12 16:07

Paul Fultz II


People also ask

Can lambda function be static?

You can't declare the lambda expression static.

Can lambda be Constexpr?

Visual Studio 2017 version 15.3 and later (available in /std:c++17 mode and later): A lambda expression may be declared as constexpr or used in a constant expression when the initialization of each data member that it captures or introduces is allowed within a constant expression.

Can a member function be static?

Static Function MembersA static member function can only access static data member, other static member functions and any other functions from outside the class. Static member functions have a class scope and they do not have access to the this pointer of the class.

Do lambda expressions have external linkage?

No. Lambdas are ultimately objects that have a function call operator. A captureless lambda can be converted into a function pointer of its appropriate type, but that function pointer will be a C++ function with C++ linkage.


Video Answer


2 Answers

This code is ill-formed. A constexpr variable is required to be initialized by a constant expression, and [expr.const]p2 says:

A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression [...]:

  • a lambda-expression

GCC is therefore incorrect to accept this code.

Here's one way to give a class a static data member of lambda type:

auto a = []{}; struct S {   static decltype(a) b; }; decltype(a) S::b = a; 
like image 185
Richard Smith Avatar answered Sep 22 '22 10:09

Richard Smith


You can make it work, in clang 3.4, as long as the lambda doesn't capture anything. The idea is directly from Pythy .

#include <type_traits> #include <iostream> template<typename T> auto address(T&& t) -> typename std:: remove_reference<T> :: type * {         return &t; }  struct A {         static constexpr auto * F = false ? address(                  [](int x){ std:: cout << "It worked. x = " << x << std:: endl;                  }         ) : nullptr; // a nullptr, but at least its *type* is useful };   int main() {     (*A::F)(1337); // dereferencing a null. Doesn't look good     return 0; } 

There are two potentially controversial bits here. First, there's the fact that A::F is constexpr, but it has a lambda in its definition.

That should be impossible right? No. A ternary expression b ? v1 : v2 can be a constexpr without requiring all three of b, v1, v2 to be constexpr. It is sufficient merely that b is constexpr along with one of the remaining two (depending on whether b is true or false. Here b is false, and this selects the final part of the ?:, i.e. nullptr.

In other words false ? a_non_constexpr_func() : a_constexpr_func() is a constexpr. This appears to be the interpretation in clang anyway. I hope this is what's in the standard. If not, I wouldn't say that clang "should not accept this". It appears to be a valid relaxation of the rule. The unevaluated part of a ?: is unevaluated and therefore it constexpr-ness shouldn't matter.

Anyway, assuming this is OK, that gives us a nullptr of the correct type, i.e. the type of a pointer to the lambda. The second controversial bit is (*A::F)(1337); where we are dereferencing the null pointer. But it is argued by the page linked above that that is not a problem:

It appears that we are derefencing a null pointer. Remember in C++ when dereferencing a null pointer, undefined behavior occurs when there is an lvalue-to-rvalue conversion. However, since a non-capturing lambda closure is almost always implemented as an object with no members, undefined behavior never occurs, since it won't access any of its members. Its highly unlikely that a non-capturing lambda closure could be implemented another way since it must be convertible to a function pointer. But the library does statically assert that the closure object is empty to avoid any possible undefined behavior.

like image 36
Aaron McDaid Avatar answered Sep 19 '22 10:09

Aaron McDaid