在 class 静态常量 ODR

In class static const ODR

const 成员的 static in-class 初始化让我有点困惑。例如,在下面的代码中:

#include <iostream>

struct Foo
{
    const static int n = 42;
};

// const int Foo::n; // No ODR

void f(const int& param)
{
    std::cout << param << std::endl;
}

int g(const int& param)
{
    return param;
}

template<int N>
void h()
{
    std::cout << N << std::endl;
}

int main()
{
    // f(Foo::n); // linker error, both g++/clang++
    std::cout << g(Foo::n) << std::endl; // OK in g++ only with -O(1,2 or 3) flag, why?!
    h<Foo::n>(); // this should be fine
}

Live example

我没有定义Foo::n(该行有注释)。所以,我预计调用 f(Foo::n) 会在 link 时失败,而且确实如此。但是,每当我使用诸如 -O1/2/3 之类的优化标志时,以下行 std::cout << g(Foo::n) << std::endl; 编译并且 link 仅由 gcc 正常(clang 仍然发出 linker 错误)。

  1. 为什么gcc(试过gcc5.2.0和gcc 4.9.3)打开优化后编译和link代码?
  2. 我说 in-class static const 成员的唯一用法是常量表达式,例如 [=] 中的模板参数18=] 调用,在这种情况下代码应该 link?

根本没有定义。 GCC 4.9.2 无法编译并且 link 带有任何标志。

请注意:

const static int n = 42;

声明初始化程序,但不是定义

我假设编译器在优化期间执行以下操作:

  • const static int n 在任何地方都是内联的。没有为变量 n 分配内存,对它的引用变得无效。函数 f() 需要对 n 的引用,因此程序未编译。

  • 函数g短小精悍。它被有效地内联和优化。优化后,函数g不需要引用n,它只是returns常量值42.

解决方法是在class外定义变量:

struct Foo
{
    const static int n;
};

const int Foo::n = 42;

形式上,ODR 违规是未定义的行为,因此编译器可以表现出它喜欢的任何行为。这就是为什么行为会随着优化级别和编译器而改变的原因——编译器没有义务维护特定的行为。

ODR 违规不需要诊断,来自 C++ 标准草案标准部分 3.2 [basic.def.odr](强调我的前进):

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required.

因此,不同优化级别的不一致行为是完全一致的行为。

非正式地,变量是 odr-used 如果:

its address is taken, or a reference is bound to it, and a function is odr-used if a function call to it is made or its address is taken. If an object or a function is odr-used, its definition must exist somewhere in the program; a violation of that is a link-time error.

所以 fg 都是 odr-uses 并且需要一个定义。

有关 odr-use 的相关 C++14 引用来自 [basic.def.odr]:

部分

A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.19) that does not invoke any nontrivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (4.1) is applied to e, or e is a discarded-value expression [...]

C++11中的写法类似,从C++11到C++14的变化体现在defect report 712

在 C++11 之前是