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

问题描述

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

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

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

int g();
struct X {
  static int g();
};
struct Y : X {
  static int i;
};
int Y::i = g();                 // equivalent to Y::g();

规范中的确切措辞和示例多年来发生了变化,由于某些未知原因,C++11C++14 具有与上述非常相似的示例,C++17 和 {{ 3}} 首选从静态数据成员初始化的示例。 C++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;
}

输出:

10 42 -1 999

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

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

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

10 10 -1 -1

注意:doesn't work well 以及 inheritancea template class without inheritance 的行为似乎都可以正常工作。

解决方法

GCC 和 Clang 都是正确的。

特别是来自[temp.dep]/3 [强调我的]:

在类或类模板的定义中,一个作用域 在非限定名称查找期间不检查依赖基类 在类的定义点模板或成员或 在类模板或成员的实例化期间。 [ 示例:

typedef double A;
template<class T> class B {
  typedef int A;
};
template<class T> struct X : B<T> {
  A a;              // a has type double
};

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

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

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

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

您正在使用 injected-class-name ([temp.local]/3) 来访问 从属名称 ([temp.res]/9) fooz 来自 base 类模板,而在静态数据成员 x2y2 的定义中使用的非限定名称不会检查依赖基类的范围.


注意,我们可能会将依赖的 base 类的名称带入派生类的作用域中,将 foobar 重新定义为

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

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

10 10 -1 -1
,

这只是非依赖名称绑定规则的另一种情况,在模板定义点查找和绑定。 (不要与 ADL 混淆!即使 ADL 会帮助调用 foo(),如果它有任何参数。)
有关完整的详细信息,请阅读此处:https://en.cppreference.com/w/cpp/language/dependent_name

从模板基类调用函数会发生类似的工件:

template <typename T>
struct A
{
    void f(){}
};

template <typename T>
struct B : A<T>
{
    void g()
    {
        A<T>::f();     // of course works
        this->f();     // this-> makes it depended,so it's bound only on instantiation time

        f();           // compilation error,no f() found

        using A<T>::f; // make the name available (and depended on the template)
        f();           // now it works
    }
};

https://gcc.godbolt.org/z/39KGvM

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...