lambda as a static member

2019-02-16 04:13发布

问题:

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?

回答1:

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;


回答2:

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.