要在派生类模板中使用依赖基类的名称(例如
B<T>
),必须向编译器添加一个突出显示的前缀,该前缀是依赖名称(B<T>::
)。为了避免多次这样做,可以使用 using 声明来代替。
下面带有此类 using 声明的代码:
template <class T>
struct A {
constexpr static int x = 0;
};
template <class T>
struct B : A<T> {
// ok everywhere
constexpr static int y = B<T>::x;
// ok in MSVC
using B<T>::x;
constexpr static int z = x;
};
被发现在 MSVC 编译器中运行良好,而其他编译器则不喜欢它。 Clang特别抱怨
错误:“B”中没有名为“x”的成员
Hovewer Clang 在上面的
y = B<T>::x
行中没有看到任何错误。在线演示:https://gcc.godbolt.org/z/PvKW753M8
这里哪种实现是正确的,为什么?
这是一个有趣的案例,突出了不同编译器之间 C++ 模板处理的一些细微差别。让我们分解这个问题并解释哪种实现是正确的。
问题源于不同编译器如何解释模板类中的依赖名称。在这种情况下,名称“x”取决于模板参数 T,因为它来自基类 A。
根据C++标准,正确的实现是Clang和GCC遵循的实现。 MSVC 在这种情况下的行为是不标准的。
原因如下:
行
constexpr static int y = B<T>::x;
是正确的,并且适用于所有编译器,因为它用 B<T>::
显式限定“x”,清楚地表明“x”是一个从属名称。
使用声明
using B<T>::x;
是有问题的,因为B<T>
指的是当前正在定义的类。标准不允许类在 using 声明中以这种方式引用自身。
行
constexpr static int z = x;
在符合标准的编译器中失败,因为“x”没有显式限定为从属名称,并且之前的 using 声明无效。
MSVC 接受此代码可能是由于非标准扩展或对模板中有关从属名称的规则的更宽松的解释。虽然这看起来很方便,但当使用其他符合标准的编译器编译代码时,可能会导致可移植性问题。
要使此代码在所有符合标准的编译器上工作,您应该使用以下方法之一:
template <class T>
struct B : A<T> {
constexpr static int y = B<T>::x;
constexpr static int z = B<T>::x;
};
template <class T>
struct B : A<T> {
using A<T>::x;
constexpr static int y = x;
constexpr static int z = x;
};
这两种方法都可以在所有符合标准的编译器上正常工作。
在这种情况下,Clang 和 GCC 正在根据 C++ 标准实现正确的行为。 MSVC 对原始代码的接受是非标准的,不应依赖于可移植代码。在模板类中使用依赖名称时,显式限定它们或使用引用基类而不是正在定义的类的 using 声明非常重要。
希望有帮助!如果您需要更多帮助,请告诉我。