【问题标题】:Assign static constexpr class member to runtime variable将静态 constexpr 类成员分配给运行时变量
【发布时间】:2016-06-25 23:16:26
【问题描述】:

我知道有很多类似的问题,但在某种程度上是不同的问题。大概是以下几种情况:

#include <iostream>
#include <array>

template<typename T> class MyClass
{
public:
    static constexpr std::array<T,4> ARRAY {{4, 3, 1, 5}};
};

int main()
{
    constexpr std::array<int, 4> my_array(MyClass<int>::ARRAY); // works fine -> can use the ARRAY to initialize constexpr std::array

    constexpr int VALUE = 5*MyClass<int>::ARRAY[0]; // works also fine

    int value;
    value = my_array[0]; // can assign from constexpr
    value = MyClass<int>::ARRAY[0]; // undefined reference to `MyClass<int>::ARRAY

    std::cout << VALUE << std::endl;
    std::cout << value << std::endl;

    return 0;
}

据我了解,constexpr 用于编译时常量。所以编译器已经可以进行一些计算,例如计算VALUE。此外,我显然可以定义一个constexpr std::array&lt;,&gt;,我可以从中将值分配给运行时变量。我希望编译器已经将value = 4 设置到可执行程序中,以避免加载操作。但是,我不能直接从静态成员分配,得到错误

undefined reference to `MyClass<int>::ARRAY'
clang-3.7: error: linker command failed with exit code 1

这对我来说毫无意义,因为它可以通过另一个 constexpr 变量的中间步骤来完成。

所以我的问题是:为什么不能将类的静态 constexpr 成员分配给运行时变量?

注意:在我的 MWE 中,该类是一个模板类,不会影响错误。不过,我最初对这种特殊情况很感兴趣,我希望它能够像非模板类那样更通用。

(编译器是 clang++g++-std=c++11 - 它们给出相同的错误)

编辑:@Bryan Chen:忘记了输出行。现已添加。

【问题讨论】:

  • clang++ 有这个问题:coliru.stacked-crooked.com/a/e9698f2bb249e509。但是g++ 有效:coliru.stacked-crooked.com/a/5ef23fe29b0aaa28。 Clang 错误?
  • VS2015 这么说:in-class initialization for type 'const std::array&lt;int,4&gt;' is not yet implemented; static member will remain uninitialized at runtime but use in constant-expressions is supported。您可能会遇到类似的情况。
  • @Bryan:我不精确。我安装了 g++4.8.5,它还不支持 C++14。但我希望这已经适用于 C++11!?
  • 我不知道template 是否会破坏这种特殊情况或更新的标准,但如果这是一个正常的class 声明,您仍然需要定义 i> static 成员,即使是常量(除非它是 int 或其他类似类型),不是吗?
  • Similar question - 没有发布令人满意的答案

标签: c++ templates c++11 constexpr one-definition-rule


【解决方案1】:

undefined reference 是链接器错误。规则是,如果一个变量是odr-used,那么它必须有一个定义。这甚至适用于 constexpr 变量。

与大多数 ODR 规则一样,违反它是未定义的行为,无需诊断(这可以解释为什么您在某些使用值时没有看到诊断)。

要修复错误,请在类外添加定义:

template<typename T> constexpr std::array<T,4> MyClass<T>::ARRAY;

由于它是一个模板,您实际上可以将它放在标题中,而不是通常情况下,定义正好放在一个 .cpp 文件中。


这里的主要问题是ARRAY[0] 是否算作odr-use。根据this detailed post,在 C++11 和 C++14 中,索引数组确实算作 odr-use ,但这被 DR 1926 针对 C++14 提出的更改为不是odr-use

然而,那是在谈论内置数组。 IDK 是否同样的理由适用于std::array,我发现 [basic.def.odr]/3 的文字难以理解。根据informal definition on cppreferencestd::array::operator[] 会导致数组的odr-use,因为它的返回值绑定了对数组的引用。

【讨论】:

  • 谢谢你的好答案。我是否理解正确,在编译时对其他 constexpr 对象的使用是 not odr-use,因此不需要定义?换句话说:编译器可以使用operator[] 计算VALUE,因为它不需要引用is(因此没有定义),但在运行时value 无法计算,因为需要地址,因此需要定义是?
  • @marlam 我认为它们都是 odr-use,但它只是编译器的一些细节,我不知道它在一种情况下会导致错误,而在另一种情况下不会。
【解决方案2】:

因此,我总是从 constexpr 函数返回 constexpr 对象。

修改后的代码如下。请注意,由于 std::array&lt;&gt; 中存在 c++14 缺陷,您必须返回 const std::array 以允许 operator[] 工作。

#include <iostream>

#include <iostream>
#include <array>

template<typename T> class MyClass
{
public:
    static constexpr const std::array<T,4> ARRAY() { return {4, 3, 1, 5}; };
};

int main()
{
    constexpr std::array<int, 4> my_array(MyClass<int>::ARRAY()); // works fine -> can use the ARRAY to initialize constexpr std::array

    constexpr int VALUE = 5 * MyClass<int>::ARRAY()[0]; // works also fine

    int value;
    value = my_array[0]; // can assign from constexpr
    value = MyClass<int>::ARRAY()[0]; // undefined reference to `MyClass<int>::ARRAY

    std::cout << VALUE << std::endl;
    std::cout << value << std::endl;

    return 0;
}

预期结果:

20
4

【讨论】:

  • 谢谢!我是否理解正确,下标运算符适用于编译时计算但不适用于运行时?这是std::array 的特殊缺陷还是这种情况出现得更频繁(正如您所说,您还从 constexpr 函数返回 constexpr 对象)。这是最有可能解决的问题还是有理由保持原样?最后:通过函数获取数组不是设计缺陷吗?
  • @marlam 已在 c++17 中修复。这是因为 std::array 的 operator[] 在可变情况下不是 constexpr (接口是在 c++11 中定义的,当 constexpr 对象能够可变时,他们忘记在 c++14 中更新它)。因此,下标运算符目前在任何情况下都适用于非 constexpr 数组,并且适用于 const constexpr 数组。这种缺陷并不常见,从 c++14 开始编写 constexpr 对象的人(比如我)都知道它们可以是可变的
猜你喜欢
  • 1970-01-01
  • 2018-08-06
  • 1970-01-01
  • 2021-08-07
  • 1970-01-01
  • 2019-08-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多