4

考虑以下简单的类,这些类是我根据我在实际项目中看到的问题设计的。constexprTriple 是与Foo 类中的内部 s 一起使用的快速样板类型:

#include <iostream>

class Triple {
public:
    friend
    std::ostream & operator <<(std::ostream & o, Triple const & t);

    constexpr Triple() : a_(0), b_(0), c_(0) { }
    constexpr Triple(Triple const & other) = default;
    constexpr Triple(double a, double b, double c)
      : a_(a), b_(b), c_(c)
    { }

    ~Triple() = default;

private:
    double a_, b_, c_;
};

std::ostream & operator <<(std::ostream & o, Triple const & t) {
    o << "(" << t.a_ << ", " << t.b_ << ", " << t.c_ << ")";
    return o;
}

class Foo {
public:
    Foo() : triple_(defaultTriple) { }

    Triple const & triple() const { return triple_; }
    Triple & triple() { return triple_; }

    constexpr static float defaultPOD{10};
    constexpr static Triple defaultTriple{11.0, 22.0, 33.0};

private:
    Triple triple_;
};

如果我随后编写一个main()函数来使用来自 的公共内部constexprs Foo,如下所示,它将无法链接(使用 g++ 4.7.0,在 Windows 7 上通过 mingw-x86-64 的方式):

int main(int argc, char ** argv) {
    using std::cout;
    using std::endl;

    cout << Foo::defaultPOD << endl;
    cout << Foo::defaultTriple << endl;
}
    $ g++ -o test -O3 --std=c++11 test.cpp
    e:\temp\ccwJqI4p.o:test.cpp:(.text.startup+0x28): undefined reference to `Foo::defaultTriple' collect2.exe: error: ld returned 1 exit status

但是,如果我写

cout << Triple{Foo::defaultTriple} << endl

而不是简单地

cout << Foo::defaultTriple << endl

它将链接并运行良好。我可以看到前者更明确地表达了编译时文字的意图,但我仍然很惊讶后者不能正常工作。这是一个编译器错误,还是基于规则的原因constexpr,只有第一个示例才能工作?

我会尝试其他编译器以获得更多见解,但目前 GCC 4.7.0 是我唯一可以访问的支持constexpr.

另请注意,pod 的表达式在constexpr没有显式文字包装的情况下可以正常工作,例如cout << Foo::defaultPOD从未给我带来麻烦。

4

3 回答 3

3

出现在不需要常量表达式的上下文中的常量表达式可以在程序翻译期间进行评估,但它不是必需的,因此可以在运行时进行评估。

如果constexpr static在程序翻译期间评估成员,编译器可以使用其初始化程序来确定其值,并且不需要成员的定义。

如果该成员在运行时评估的上下文中使用,则需要其定义。

cout << Foo::defaultTriple << endl您的编译器中生成代码以在运行时执行左值到右值的转换,Foo::defaultTriple因此对象需要定义。

cout << Triple{Foo::defaultTriple} << endl编译器在程序翻译期间进行评估以创建本身可能在运行时评估Foo::defaultTriple的临时。Triple

除非您的constexpr对象仅在需要常量表达式的上下文中进行评估,否则您必须为它们提供定义。

于 2012-06-12T06:58:40.873 回答
2

defaultPOD并且defaultTriple在类中声明不是定义。如果您想在需要知道其地址的地方使用它们,则必须在类声明之外定义它们。

那么为什么cout << Foo::defaultPOD << endl;有效,但cout << Foo::defaultTriple << endl;没有呢?

defaultPOD被声明为 a float,所以当你这样做时,cout << Foo::defaultPOD它会调用 the operator<<(float val);,它的参数是 value。此调用不需要定义,因为您只使用该(它不是 3.2.3 定义的 odr-used)。如果您尝试传递Foo::defaultPOD给接受引用的函数,则需要定义它。

但是,Foo::defaultTriple失败是因为operator <<需要定义一个Triple引用Foo::defaultTriple。但是,即使在将 更改operator<<为按值传递之后,在我的测试中,我仍然会遇到链接器错误。只有当我从中删除成员变量Triple并按operator<<值传递时,代码才会在不定义静态成员变量的情况下编译。(当您从Triple编译器中删除成员变量时,我相信会优化变量)。

(这是一个很好的参考资料,解释了其中的一些内容)。

于 2012-06-12T01:33:56.677 回答
1

错误来自链接器,它找不到Foo::defaultTriple静态成员。

这里的问题是“声明”和“定义”之间的区别。你的类中的静态行是声明,你还需要一个定义。在 C++ 中,static在 a 中定义的每个字段class也应该出现在 .cpp 文件中:

// .hpp

class X {
    static int Q;
};

// .cpp

int X:Q = 0;

在您的情况下,您应该在 .cpp 文件中的某处有这一行:

Triple foo::defaultTriple;
于 2012-06-12T01:22:32.097 回答