使用 lambda 和 constexpr-if 初始化静态 bool 会给出地址错误

Initialization of static bool with lambda and constexpr-if gives adress error

我试图实现某种类型特征,但遇到了以下错误。

这是一个显示行为的简单程序:

#include <iostream>

template<typename T>
struct my_floating_point // just to make a case
{
    // static constexpr bool value = std::is_floating_point_v<T>;
    // THIS WORKS

    static constexpr bool value = []()
    {
        if constexpr(std::is_floating_point_v<T>) { return true; }
        else { return false; }
    }; // compilation error
};

int main()
{
    std::cout << my_floating_point<int>::value << std::endl;
    std::cout << my_floating_point<double>::value << std::endl;
    std::cout << my_floating_point<char>::value << std::endl;
}

注释行工作得很好。 但是,未注释的代码段给出了此警告 g++:

g++ -std=c++17 -O3 main.cpp -Wall -Wextra -pedantic
main.cpp: In instantiation of ‘constexpr const bool my_floating_point<int>::value’:
main.cpp:18:39:   required from here
main.cpp:9:24: warning: the address of ‘static constexpr bool my_floating_point<int>::<lambda()>::_FUN()’ will never be NULL [-Waddress]
  static constexpr bool value = []()
                        ^~~~~
main.cpp:9:24: warning: the address of ‘static constexpr bool my_floating_point<int>::<lambda()>::_FUN()’ will never be NULL [-Waddress]

并且运行它输出

1
1
1

怎么了?

注意: clang++ 由于某些原因没有给出警告,但输出相同。

 []()
    {
      // ...
    }

lambda 表达式的计算结果为匿名类型的实例。这就是 lambda 的真正含义:一个未指定类型的对象。尝试将此对象的实例分配给 bool 是无效的 C++。

好消息是这个匿名类型有一个 operator() 重载,这只是意外地发生在 return 一个 bool 上。因此,例如,这将起作用:

static constexpr bool value = []()
{
    if constexpr(std::is_floating_point_v<T>) { return true; }
    else { return false; }
}(); // compiles just fine.

总结: 简化以获得更具可读性的警告消息,然后将该消息与可能更好识别的案例进行比较。这与其说是直接解释(授人以鱼),不如说是一种解决方法(授之以渔)。


这是真正以 最低限度 reproducible example 工作的情况之一。我承认保留了一些并发症“只是为了证明”,但我们暂时放弃它。

main 中的三行每行触发相同的警告,因此简化为其中一行。最好,我们保留输出意外的一行,例如三行中的第一行(int 不是浮点类型,因此 my_floating_point<int>::value 旨在为假)。这会将编译器的消息减少到原来的三分之一,并有助于识别哪些消息在一起(此时,它们都在一起)。

只考虑一种类型,我们不再需要my_floating_point成为模板。模板有时会在警告中添加大量无关紧要的细节(例如 - 在这种情况下 - required from here 消息),因此删除模板并将 std::is_floating_point_v<T> 替换为std::is_floating_point_v<int>。这将警告减少到一条消息加上警告发生位置的指示。

进一步简化:将 value 放在 struct 之外,从而消除 my_floating_point。不可否认,这对错误消息没有太大影响,但它确实从示例代码中删除了一个红色鲱鱼。

#include <iostream>

static constexpr bool value = []()
{
    if constexpr(std::is_floating_point_v<int>) { return true; }
    else { return false; }
}; // compilation warning

int main()
{
    std::cout << value << std::endl;
}
prog.cc:7:1: warning: the address of 'static constexpr bool<lambda()>::_FUN()' will never be NULL [-Waddress]
    7 | }; // compilation warning
      | ^

请注意,这几乎是问题编译器输出的其中一行。唯一的区别是删除了 my_floating_point<int>::,这与我们所做的简化非常吻合。


好的,现在我们已经适当简化了情况,让我们将警告与在略有不同的设置中产生的警告进行比较。让我们使用官方函数代替 lambda。目标是通过比较相似的场景来获得一些基本的(但不精确的)理解。

不要纠结于“我会怎么想做这件事?”这是我知道要做的事情,因为我想起了来自不同设置的非常相似的警告。这个答案的第一部分提出了我认为可以合理期望其他人采取的步骤。相比之下,此答案的这一部分需要熟悉来自编译器的消息。

#include <iostream>

bool fun()
{
    if constexpr(std::is_floating_point_v<int>) { return true; }
    else { return false; }
}

static constexpr bool value = fun; // compilation warning

int main()
{
    std::cout << value << std::endl;
}
prog.cc:9:31: warning: the address of 'bool fun()' will never be NULL [-Waddress]
    9 | static constexpr bool value = fun; // compilation warning
      |                               ^~~

同样的警告,[something] 的地址永远不会为 NULL。 (也许您已经知道这是怎么回事了?)在这段代码中,由于未调用该函数,因此符号 fun 成为指向该函数的指针。当且仅当指针为非空时,将指针分配给 bool 会导致 true。这个特定的指针不能为空,因为它是某物的地址。所以编译器抛出一个警告;该代码在语法上是有效的,但您可能打算调用该函数(通过在分号前添加括号,如 fun() 而不仅仅是 fun)。

具有 lambda 的代码正在使用类似的机制。当 lambda 未被调用时,它可以转换为 bool,成为 true 当且仅当存在 lambda 时。与函数情况一样,结果必须是 true。编译器抛出警告;该代码在语法上是有效的,但您可能打算调用 lambda(通过在分号前添加括号)。

static constexpr bool value = []()
{
    if constexpr(std::is_floating_point_v<int>) { return true; }
    else { return false; }
}(); // no compilation warning with the parentheses

更好的是,现在的输出是 0,如预期的那样。


好的,简短的版本是“添加括号”,但这个答案的真正教训是识别编译器消息的有用性。当警告的措辞与您之前遇到的警告相似时,请尝试复制您熟悉的警告。看看是否有相似之处可以让您将解决方案从熟悉的案例转移到不太熟悉的案例。

熟悉需要时间,但如果你把它作为一个目标,花费的时间会更少。 ;)