Лямбда как статический член
Я пытаюсь использовать лямбда как статический член, например:
struct A
{
static constexpr auto F = [](){};
};
int main()
{
A::F();
return 0;
}
Это даже правильный код С++ 11? На clang, я получаю эту ошибку:
error: constexpr variable 'F' must be initialized by a constant
expression
static constexpr auto F = [](){};
^~~~~~
Кажется, что в clang lambdas не считаются постоянным выражением. Это верно? Возможно, они еще не полностью реализовали lambdas в clang, потому что gcc 4.7, по-видимому, разрешает его как constexpr
, но он дает еще одну ошибку:
error: ‘constexpr const<lambda()> A::F’, declared using local type ‘const<lambda()>’, is used but never defined
Я не уверен, я понимаю, что это значит. Кажется, правильно вывести тип лямбды, но он только объявляет его и не определяет. Как я могу определить его?
Ответы
Ответ 1
Этот код плохо сформирован. A constexpr
требуется инициализировать константное выражение, а [expr.const]p2
говорит:
Условное выражение является выражением основной константы, если оно не связано с одним из следующих как потенциально оцененное подвыражение [...]:
Поэтому GCC неправильно принимает этот код.
Здесь один из способов дать классу статический элемент данных лямбда-типа:
auto a = []{};
struct S {
static decltype(a) b;
};
decltype(a) S::b = a;
Ответ 2
Вы можете заставить его работать, в clang 3.4, до тех пор, пока лямбда ничего не захватывает. Идея прямо из 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;
}
Здесь есть два возможных противоречивых бита. Во-первых, существует тот факт, что A::F
является constexpr
, но в его определении есть лямбда.
Это должно быть невозможно? Нет. Тройное выражение b ? v1 : v2
может быть constexpr
, не требуя, чтобы все три из b
, v1
, v2
были constexpr
. Достаточно просто, чтобы b
был constexpr
вместе с одним из оставшихся двух (в зависимости от того, является ли b
true
или false
. Здесь b
есть false
, и это выбирает заключительную часть ?:
, т.е. nullptr
.
Другими словами, false ? a_non_constexpr_func() : a_constexpr_func()
есть constexpr
. По-видимому, это интерпретация в clang. Надеюсь, это то, что в стандарте. Если нет, я бы не сказал, что clang "не должен принимать это". Это, по-видимому, действительное ослабление правила. Неоценимая часть a ?:
не оценена, и поэтому ее значение constexpr
не имеет значения.
В любом случае, считая это ОК, это дает нам nullptr
правильного типа, т.е. тип указателя на лямбда. Второй противоречивый бит (*A::F)(1337);
, где мы разыскиваем нулевой указатель. Но на странице указано, что это не проблема:
Похоже, что мы дефрагментируем нулевой указатель. Помните, что в С++ при разыменовании нулевого указателя поведение undefined происходит при преобразовании lvalue-to-rvalue. Однако, поскольку незахватывающее лямбда-закрытие почти всегда реализуется как объект без членов, поведение undefined никогда не происходит, поскольку оно не будет иметь доступа к любому из его членов. Его очень маловероятно, что не захватывающее лямбда-замыкание может быть реализовано иначе, поскольку оно должно быть преобразовано в указатель функции. Но библиотека статически утверждает, что объект закрытия пуст, чтобы избежать возможного поведения undefined.