【问题标题】:C++ Linker Error With Class static constexpr类静态 constexpr 的 C++ 链接器错误
【发布时间】:2012-01-17 04:27:43
【问题描述】:

我正在用g++-4.6.1 --std=c++0x 编译以下简单程序:

#include <algorithm>

struct S
{
    static constexpr int X = 10;
};

int main()
{
    return std::min(S::X, 0);
};

我收到以下链接器错误:

/tmp/ccBj7UBt.o: In function `main':
scratch.cpp:(.text+0x17): undefined reference to `S::X'
collect2: ld returned 1 exit status

我意识到内联定义的静态成员没有定义符号,但我的(可能有缺陷的)印象是使用constexpr 告诉编译器始终将符号视为表达式;因此,编译器会知道传递对符号 S::X 的引用是不合法的(出于同样的原因,您不能引用文字 10)。

但是,如果 S 被声明为命名空间,即“命名空间 S”而不是“结构 S”,则一切都可以正常链接。

这是 g++ 错误还是我仍然需要使用技巧来解决这个烦恼?

【问题讨论】:

    标签: c++ linker c++11 constexpr


    【解决方案1】:

    我不认为这是一个错误。如果您将constexpr 更改为const,它仍然会失败,并出现完全相同的错误。

    您已经声明了S::X,但没有在任何地方定义它,所以没有存储空间。如果你对它做任何需要知道它的地址的事情,那么你也需要在某个地方定义它。

    例子:

    int main() {
          int i = S::X; // fine
          foo<S::X>(); // fine
          const int *p = &S::X; // needs definition
          return std::min(S::X, 0); // needs it also
    }
    

    原因是constexpr可以在编译时进行评估,但要求这样评估,同样可以在运行时进行。它没有指示“编译器始终将符号视为表达式”,它暗示如果编译器愿意,这样做是明智和允许的。

    【讨论】:

    • 您只需要定义S::X,因为std::min 通过引用获取其参数。如果它按值取参数,则不需要定义,因为根据 [basic.def.odr]p2,需要在潜在评估的上下文中提到的变量定义“除非它是一个满足立即应用出现在常量表达式 (5.19) 和左值到右值转换 (4.1) 中的要求”,在这种情况下就是这样。
    • 那么,为什么它是静态结构变量或只是一个全局变量?
    • @albert 不完全确定您正在考虑的情况,但您看到的行为很可能也受到编译器正在进行的优化的影响。要么就是这样,要么你写的东西既是声明又是定义。
    • @Flexo:就像原来的问题一样。如果是namespace S,则没有链接器错误,如果是struct S,则有一个。
    • 此外,在standard 中,它说“具有命名空间范围 (3.3.6) 的名称具有内部链接,如果它是 [...] 一个明确声明为 const 的变量的名称或 constexpr ..."。据我了解,这对于结构范围内的静态名称是不同的。因此,在一种情况下,必须定义符号,在另一种情况下,必须不定义。这不受编译器优化的影响。
    【解决方案2】:

    这已在 C++17 中修复。

    https://en.cppreference.com/w/cpp/language/static:

    如果静态数据成员声明为 constexpr,则它是隐式内联的 并且不需要在命名空间范围内重新声明。这 没有初始化器的重新声明(以前需要,如图所示 以上)仍然是允许的,但已被弃用。

    【讨论】:

      【解决方案3】:

      已经解释了错误的原因,所以我只是添加一个解决方法。

      return std::min(int(S::X), 0);
      

      这会创建一个临时的,所以std::min 可以引用它。

      【讨论】:

        【解决方案4】:

        您还需要为结构(或类)之外的 constexpr 成员提供定义,但这次没有它的值。见这里:https://en.cppreference.com/w/cpp/language/static

        #include <algorithm>
        
        struct S
        {
            static constexpr int X = 10;
        };
        
        constexpr int S::X;
        
        int main()
        {
            return std::min(S::X, 0);
        };
        

        【讨论】:

          【解决方案5】:

          在 C++ 标准 (latest working draft) 中,它说:

          具有命名空间范围 (3.3.6) 的名称如果是 [...] 的名称,则该名称具有内部链接,该名称已显式声明 constconstexpr 且既未显式声明 extern 也未先前声明有外部链接[...]。

          “联动”是这样定义的:

          当一个名称可能表示相同的对象、引用、函数、类型、模板、 命名空间或值作为另一个范围内的声明引入的名称:

          ——当一个名字有外部链接时,它所表示的实体可以被来自范围的名字引用 其他翻译单元或来自同一翻译单元的其他范围。

          ——当一个名字有内部链接时,它所表示的实体可以被其他范围的名字引用 在同一个翻译单元中。

          ——当一个名称​​没有链接时,它所表示的实体不能被其他范围的名称引用。

          因此,对于namespace S,它将具有外部链接,对于struct S,它将具有内部链接

          具有外部链接的符号需要在某些翻译单元中明确定义符号。

          【讨论】:

          • 联动与本题无关。 Xstruct S 内部的声明不是定义,而在namespace S 内部它实际上是一个定义。函数min 通过引用获取其参数,因此需要定义。
          【解决方案6】:

          你对constexpr的理解是错误的。已声明的左值 constexpr 仍然是一个左值,并且声明了一个函数 constexpr 仍然是一个函数。当一个函数有 一个引用参数,它被传递一个左值,语言 要求引用引用该左值,并且什么都没有 别的。 (当应用于int 类型的变量时,有 constexpr 和普通的区别真的很小 const.)

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-04-19
            • 2018-11-06
            • 2015-10-31
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多