静态constexpr成员似乎与std::min不兼容

4

这里有一个问题,它的原因对我来说相当模糊,但是解决它的方法非常简单。

考虑下面的代码(让我称之为我的main.cpp):

#include <algorithm>

struct Foo {
    static constexpr float BAR = .42;

    float operator()() const noexcept {
        float zero = .0;
        return std::min(zero, BAR);
    }
};

int main() {
    Foo foo;
    foo();
}

当我试图编译它时,出现了以下错误:

foobar:~/stackoverflow$ g++ -std=c++11 main.cpp
/tmp/ccjULTPy.o: 在函数 'Foo::operator()() const' 中:
main.cpp:(.text._ZNK3FooclEv[_ZNK3FooclEv]+0x1a): 对 `Foo::BAR' 未定义的引用
collect2: error: ld 返回 1 退出状态

很明显,如果我使用以下语句,情况也是一样的:
return std::min(zero, Foo::BAR);

以下是上述示例的略微修改版本。
尽管仍然引用BAR成员,但这个版本编译时不会出现错误:

#include <algorithm>

struct Foo {
    static constexpr float BAR = .42;

    float operator()() const noexcept {
        float zero = .0;
        float bar = BAR;
        return std::min(zero, bar);
    }
};

int main() {
    Foo foo;
    foo();
}

我没有成功理解为什么后者能够编译成功,而前者却以错误结束。据我所知,这两个版本都是正确的并且应该能够编译通过,但我强烈怀疑自己在这里遗漏了一些重要的东西。有任何建议吗?这是我的编译器版本:g++ (Debian 5.3.1-5) 5.3.1 20160101

1
我无法复现。 - NathanOliver
1
std::min通过引用传递参数,ODR使用静态成员,您需要相应的定义。 - Piotr Skotnicki
@skypjack,您只声明了一个静态的constexpr成员,但是没有定义它。然而,要将其传递给std::min,它必须具有地址(因为它是通过引用进行传递的)。如果您仅将其值分配给局部变量,则它是左值到右值转换,这不会ODR使用静态数据成员,因此可以工作。 - Piotr Skotnicki
1
相关链接:https://dev59.com/TV0b5IYBdhLWcg3wQPbw - vsoftco
@PiotrSkotnicki 这是有道理的,但奇怪的是,如果你看一下NathanOliver评论中的链接,实际上它确实可以编译通过,所以我猜我们在这个推理过程中漏掉了些什么。我错了吗? - skypjack
显示剩余6条评论
1个回答

5

“min”的选择原型是:

template<class T> 
/* constexpr since C++14 */ const T& min( const T& a, const T& b );

重要的一点是它通过引用传递参数,这意味着它使用 One-Definition-Rule (ODR)。
而你从未定义它,你只在类中声明了它(带有初始化器):
    static constexpr float BAR = .42;

这足以复制和使用该值,但不能将其用作除prvalue之外的任何内容。

请参见为什么constexpr静态成员(类类型)需要定义?

违反ODR(其细节确实非常丰富)无需诊断:

3.2 一个定义规则 [basic.def.odr]

4 每个程序都必须包含在该程序中odr-used的每个非内联函数或变量的正好一个定义;不需要诊断。定义可以显式地出现在程序中,可以在标准或用户定义库中找到,或者(在适当的情况下)是隐式定义的(请参见12.1、12.4和12.8)。内联函数应该在每个使用它的翻译单元中定义。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接