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

问题描述

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

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

#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() 重载,它只是偶然地返回一个布尔值。因此,例如,这将起作用:

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> 替换为 {{1 }}。这将警告减少到一条消息加上警告发生位置的指示。

另一种简化:在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 | ^ ,这与我们所做的简化非常吻合。


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

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

my_floating_point<int>::
#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;
}

同样的警告,[something] 的地址永远不会为 NULL。 (也许你已经看到这是怎么回事?)在这段代码中,由于函数没有被调用,符号 prog.cc:9:31: warning: the address of 'bool fun()' will never be NULL [-Waddress] 9 | static constexpr bool value = fun; // compilation warning | ^~~ 变成了一个指向函数的指针。当且仅当指针为非空时,将指针分配给 fun 会导致 bool。这个特定的指针不能为空,因为它是某物的地址。所以编译器抛出一个警告;代码在语法上是有效的,但您可能打算调用该函数(通过在分号前添加括号,如 true 而不是 fun())。

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

true

更好的是,输出现在是 static constexpr bool value = []() { if constexpr(std::is_floating_point_v<int>) { return true; } else { return false; } }(); // no compilation warning with the parentheses ,符合预期。


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

熟悉需要时间,但如果您将其定为目标,则花费的时间会更少。 ;)