带有模板化基类的“静态数据成员的定义在其类的范围内”的规范规则

Ami*_*rsh 6 c++ static scope definition language-lawyer

C++ 规范(例如C++17 [class.static] §2)说:

静态数据成员的定义在其类的范围内。

举个例子(取自C++14 规范):

int g();
struct X {
  static int g();
};
struct Y : X {
  static int i;
};
int Y::i = g();                 // equivalent to Y::g();
Run Code Online (Sandbox Code Playgroud)

规范中的确切措辞和示例多年来发生了变化,由于某些未知原因,C++11C++14具有与上述非常相似的示例,C++17C++20更喜欢初始化示例来自静态数据成员。在C ++ 20措辞也更准确。但似乎这条规则并没有实际的变化。


似乎这条规则不适用于模板化类的继承:

template<int VALUE>
struct base {
    static int foo() { return VALUE; }
    static constexpr int z = -1;
};

template<int VALUE>
struct foobar: base<VALUE> {
    static int x1;
    static int x2;
    static int y1;
    static int y2;
};

int foo() { return 42; }

int z = 999;

template<int VALUE>
int foobar<VALUE>::x1 = foobar::foo();

template<int VALUE>
int foobar<VALUE>::x2 = foo();

template<int VALUE>
int foobar<VALUE>::y1 = foobar::z;

template<int VALUE>
int foobar<VALUE>::y2 = z;

int main() {
    std::cout << foobar<10>::x1 << ' ' << foobar<10>::x2 << ' '
              << foobar<10>::y1 << ' ' << foobar<10>::y2;
}
Run Code Online (Sandbox Code Playgroud)

输出:

10 42 -1 999
Run Code Online (Sandbox Code Playgroud)

GCC 10.2 和 Clang 5.0.0 都同意上述(令人惊讶的?)输出。

它是正确的输出,还是两个编译器中的错误?

预期的输出,期望foo()在静态成员初始化的上下文中调用 将表现为调用foobar::foo()等,应该是:

10 10 -1 -1
Run Code Online (Sandbox Code Playgroud)

注意:继承的行为以及没有继承的模板类带有非模板派生的模板基的行为,似乎都可以正常工作。

dfr*_*fri 4

GCC 和 Clang 都是正确的。

\n

特别是,来自[temp.dep]/3 [重点是我的]:

\n
\n

在类或类模板的定义中,在非限定名称查找期间,无论是在类模板或成员的定义点,还是在类模板或成员的实例化期间,都不会检查依赖基类的范围。[\xe2\x80\x89示例:

\n
typedef double A;\ntemplate<class T> class B {\n  typedef int A;\n};\ntemplate<class T> struct X : B<T> {\n  A a;              // a has type double\n};\n
Run Code Online (Sandbox Code Playgroud)\n

A定义中的类型名称X<T>绑定到typedef全局命名空间范围中定义的名称,而不是typedef基类中定义的名称class B<T>。\xe2\x80\x89\xe2\x80\x94\xe2\x80\x89结束示例\xe2\x80\x89] [...]

\n
\n

在派生类模板的静态数据成员x1和定义中:y1foobar

\n
template<int VALUE>\nint foobar<VALUE>::x1 = foobar::foo();\n\ntemplate<int VALUE>\nint foobar<VALUE>::y1 = foobar::z;\n
Run Code Online (Sandbox Code Playgroud)\n

您正在使用注入类名称[temp.local]/3)来访问依赖名称[temp.res]/9foozbase类模板中访问,而静态数据定义中使用的非限定名称成员x2,并且y2不会检查依赖基类的范围。

\n
\n

请注意,我们可以将依赖类的名称引入base派生类的范围,重新定义foobar

\n
template<int VALUE>\nstruct foobar: base<VALUE> {\n    using base<VALUE>::foo;\n    using base<VALUE>::z;\n    static int x1;\n    static int x2;\n    static int y1;\n    static int y2;\n};\n
Run Code Online (Sandbox Code Playgroud)\n

在这种情况下,你的程序的输出将是

\n
10 10 -1 -1\n
Run Code Online (Sandbox Code Playgroud)\n