为什么Foo :: innerConstexpr不会链接,但UserLiteral {Foo :: innerConstexpr}会?

时间:2012-06-11 23:47:55

标签: c++ c++11 constexpr

考虑以下简单的类,我根据我在实际项目中看到的问题设计了这些类。 Triple是一种快速的锅炉板类型,用于Foo类中的内部constexpr

#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()函数来使用来自constexpr的公共内部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从未给我带来麻烦。

3 个答案:

答案 0 :(得分:3)

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

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

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

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

cout << Triple{Foo::defaultTriple} << endl中,编译器在程序转换期间评估Foo::defaultTriple以创建可能在运行时评估的临时Triple

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

答案 1 :(得分:2)

在类中声明的

defaultPODdefaultTriple不是定义。如果要在需要知道地址的地方使用它们,则必须在类声明之外定义它们。

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

defaultPOD被声明为float,因此当您执行cout << Foo::defaultPOD时,它会调用operator<<(float val);,其值的参数。此调用中不需要定义,因为您只使用(它不是3.2.3定义的odr-used)。如果您尝试将Foo::defaultPOD传递给带引用的函数,则需要对其进行定义。

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

(这是一个nice reference,解释了其中的一些内容)。

答案 2 :(得分:1)

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

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

// .hpp

class X {
    static int Q;
};

// .cpp

int X:Q = 0;

在你的情况下,你应该把这行放在.cpp文件的某个地方:

Triple foo::defaultTriple;