静态元组类成员的constexpr存在链接器错误

5
我有以下代码:

我有以下代码:

#include <iostream>
#include <tuple>

class T
{
    public:
        using Names = std::tuple<char const*, char const*>;
        static constexpr Names names {"First", "Second"};
};

int main()
{
    std::cout << std::get<0>(T::names);
}

作为一个constexpr,我希望names能够正常工作。但是我得到了链接器错误:
编译器:
> g++ --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 6.0 (clang-600.0.56) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin14.0.0
Thread model: posix

错误:

> g++ -std=c++1y pl.cpp
Undefined symbols for architecture x86_64:
  "T::names", referenced from:
      _main in pl-377031.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

[live demo]

2个回答

7

在类中声明静态数据成员时,这并不是定义1。只有在变量被odr-used时才需要定义2std::get<>以引用的方式传递参数,将变量绑定到引用时会立即odr-use它3

只需在外部定义names

constexpr T::Names T::names; // Edit: This goes *outside* the class "as is"!

演示.


1) [basic.def]/2:

一个声明是一个定义,除非[...]它在类定义(9.2、9.4)中声明了一个静态数据成员。

2) [basic.def.odr]/4:

每个程序都必须包含对该程序中使用 odr 的每个非内联函数或变量的确切定义;不需要诊断。

3) 根据[basic.def.odr]/3:

一个变量 x,在潜在求值表达式 ex 中出现的名称是由 ex 使用 odr 的,除非将 lvalue-to-rvalue 转换(4.1)应用于 x 产生一个不调用任何非平凡函数的常量表达式(5.19),如果 x 是对象,则 ex 是表达式 e 的潜在结果集的元素之一,其中将 lvalue-to-rvalue 转换(4.1)应用于 e,或者 e 是一个被丢弃值表达式(第5节)。

这里的 id-expression T::names 指的是相关的变量。包含所有潜在结果的唯一超级表达式 eT::names 本身,因为函数调用的潜在结果集合,即 std::get<0>(T::names),是空的。然而,明显不应用 lvalue-to-rvalue 转换,并且也明显不会丢弃 T::names 的值(因为它被传递给一个函数)。
因此,这个变量使用了 odr 并需要定义。


我认为 constexpr T::Names T::names; 必须放在编译单元中,不能在被多次包含的头文件中。 - Martin York
@CrappyExperienceBye 正确 - 如果我没记错,T::names 具有外部链接。 - Columbo
因术语涉及到,我很难理解odr-use:我已经在标准和cppreference上阅读了关于可能评估的表达式、lvalue-to-rvalue转换和潜在结果的内容,但是仍感到困惑。是否有一个全面的源来用英语解释odr-use? - template boy
@templateboy https://dev59.com/MGIk5IYBdhLWcg3wDaUW@templateboy,https://dev59.com/MGIk5IYBdhLWcg3wDaUW - Columbo

2

@Columbo发布了正确的解决方案。

不幸的是,我正在尝试构建一个仅具有标题的库。该解决方案需要将静态成员编译到一个编译单元中(这就是我使用constexpr的原因,希望避免这种情况)。

所以我需要为它添加另一个细节才能使它正常工作。这只是为了分享我的解决方案:

#include <iostream>

class T
{
    public:
        using Names = std::tuple<char const*, char const*>;
        template<std::size_t index>
        static char const* getName()
        {
            static constexpr Names names {"First", "Second"};
            return std::get<index>(names);
        }
};

int main()
{
    std::cout << T::getName<0>() << "\n";
}

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