【问题标题】:lambda as a static memberlambda 作为静态成员
【发布时间】:2012-07-28 09:06:41
【问题描述】:

我正在尝试使用 lambda 作为静态成员,如下所示:

struct A
{
    static constexpr auto F = [](){};
};


int main()
{
    A::F();
    return 0;
}

这甚至是正确的 C++11 代码吗?在铿锵声中,我收到此错误:

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

在 clang 中,lambda 似乎不被视为常量表达式。它是否正确?也许他们还没有在 clang 中完全实现 lambda,因为 gcc 4.7 似乎允许它作为 constexpr,但它给出了另一个错误:

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

我不确定,我明白这意味着什么。它似乎正确地推断出 lambda 的类型,但它只声明它而不定义它。我将如何定义它?

【问题讨论】:

  • 我可以非常无助地问,呃,“你到底为什么要这样做?”这种构造会做什么,而普通的成员函数不会?
  • @Rook 保存他以编写返回类型,我们可以将static constexpr auto 噪音扔到宏中。
  • 因为 lambdas 的类型推导比成员函数好得多。
  • @Paul:这里的实际问题是您正在使用具有特别疯狂和笨拙的返回类型的函数吗?
  • @Paul:啊,现在这更有意义了。在我看来,这很像您在 C++ 的前沿操作,因此您可能不得不等待编译器赶上您。我注意到 VC++ 2012 甚至还不能管理 constexpr。你正在做的事情并没有看起来违反标准,但我很可能误读了一些东西。

标签: c++ lambda c++11 language-lawyer constexpr


【解决方案1】:

此代码格式错误。 constexpr 变量需要用常量表达式初始化,[expr.const]p2 表示:

一个条件表达式是一个核心常量表达式,除非它涉及以下之一作为潜在评估的子表达式[...]:

  • 一个 lambda 表达式

GCC 因此接受此代码是不正确的。

这是为类赋予 lambda 类型的静态数据成员的一种方法:

auto a = []{};
struct S {
  static decltype(a) b;
};
decltype(a) S::b = a;

【讨论】:

  • GCC 因此接受此代码是不正确的。 我认为这不是那么简单。这种情况需要诊断/错误吗?有许多格式错误的程序不会给出编译器错误/警告(并且有充分的理由)
  • Consexpr 不谈,有没有办法在 C++11 中创建一个 lambda 作为类的静态成员?
  • @LucDanton 常量表达式是根据核心常量表达式定义的,因此 p2 是相关的。见第 3 页。 “常量表达式”和“核心常量表达式”的区别不是“语法”与“语义”,而是,据我所知,“实际编译时已知值或引用”与“计算常量表达式的中间结果” ”。因此,非静态局部变量的地址是有效的核心常量表达式。但由于该地址的值在编译时并不真正知道,因此它不是地址常量表达式。
  • @JohanLundberg 是的,除非另有说明,否则每条规则都需要诊断,包括这条规则。见[intro.compliance]p2
  • @Paul 我认为你不走运。有一些技巧可以在模板中获取 lambda 表达式并提取其类型,例如 template&lt;typename T&gt; std::remove_reference&lt;T&gt;::type *addr(T &amp;&amp;t) { return &amp;t; } struct S { constexpr static auto *p = false ? addr([]{}) : nullptr; static decltype(*p) lambda; };,但即便如此也不可能为 S::lambda 提供初始化程序。
【解决方案2】:

只要 lambda 不捕获任何内容,您就可以在 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::Fconstexpr,但它的定义中有一个 lambda。

这应该是不可能的吧?不可以。三元表达式b ? v1 : v2 可以是constexpr,而不需要bv1v2 中的所有三个都是constexpr。只要bconstexpr 以及其余两个中的一个 就足够了(取决于btrue 还是false。这里bfalse ,这会选择?:的最后一部分,即nullptr

换句话说,false ? a_non_constexpr_func() : a_constexpr_func()constexpr。无论如何,这似乎是clang中的解释。我希望这是标准中的内容。如果不是,我不会说clang“不应该接受这个”。这似乎是对规则的有效放宽。 ?: 的未评估部分未评估,因此 constexpr-ness 无关紧要。

无论如何,假设这没问题,这给了我们一个正确类型的nullptr,即指向 lambda 的指针的类型。第二个有争议的位是(*A::F)(1337);,我们在其中取消引用空指针。但上面链接的page 认为这不是问题:

看来我们正在解除对空指针的引用。请记住,在 C++ 中取消引用空指针时,当存在左值到右值的转换时会发生未定义的行为。但是,由于非捕获 lambda 闭包几乎总是作为没有成员的对象来实现的,因此永远不会发生未定义的行为,因为它不会访问其任何成员。非捕获 lambda 闭包极不可能以另一种方式实现,因为它必须可转换为函数指针。但是库确实静态断言闭包对象为空,以避免任何可能的未定义行为。

【讨论】:

  • 该页面缺少关于未定义行为的要点。当您取消引用空指针时会发生这种情况。现在未定义的行为可能不是 noticeable ,例如因为 lambda 没有成员,但这是一个实现细节。它是 UB,因为标准是这样说的。
  • @MSalters 这过于简单化了。标准并没有说(*(E1)).E2 为空时是未定义的行为。我相信未定义的行为只发生在左值到右值的转换时。所以&amp;*(p=0) 不是未定义的行为。请参阅here 进行讨论。
  • 谢谢你!我需要获取采用已知模板参数的 lambda 的返回类型,这似乎是这样做的唯一方法(至少从 C++11 开始)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-31
  • 2014-08-07
相关资源
最近更新 更多