奇怪的嵌套类部分特化结果在 gcc 和 clung 上
Weird nested class partial specialization results on both gcc and clang
在编写一个供个人使用的小型模板元编程库时,我遇到了一个有趣的问题。
由于我为某些元函数重用了一些部分专用化,因此我决定将它们放在一个通用模板类下,并使用标签和嵌套的部分专用化来提供行为差异。
问题是我得到了荒谬的(对我来说(结果。这是一个最小的示例,展示了我正在尝试做的事情:
#include <iostream>
#include <cxxabi.h>
#include <typeinfo>
template <typename T>
const char * type_name()
{
return abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, nullptr);
}
template <typename... Args>
struct vargs {};
namespace details
{
template <typename K>
struct outer
{
template <typename Arg>
struct inner
{
using result = Arg;
};
};
}
struct tag {};
namespace details
{
template <>
template <typename Arg, typename... Args>
struct outer<tag>::inner<vargs<Arg, Args...>>
{
using result = typename outer<tag>::inner<Arg>::result;
};
}
template <typename T>
using test_t = typename details::outer<tag>::inner<T>::result;
int main()
{
using t = test_t<vargs<char, int>>;
std::cout << type_name<t>() << 'n';
return 0;
}
使用 5.1.0 版本的 gcc 时,我vargs<char, int>
作为输出,使用 3.6.0 版本的 clang 时tag
。我的目的是让上面的代码打印char
所以我对这些结果感到非常困惑。
上述代码是合法的还是表现出未定义的行为?如果它是合法的,根据标准,预期的行为是什么?
您的代码是正确的;类外隐式实例化的类模板成员类模板部分专用化旨在由标准允许,只要它们足够早地定义。
首先,让我们尝试一个最小的例子 - 顺便注意这里没有任何需要 C++11 的内容:
template<class T> struct A {
template<class T2> struct B { };
};
// implicitly instantiated class template member class template partial specialization
template<> template<class T2>
struct A<short>::B<T2*> { };
A<short>::B<int*> absip; // uses partial specialization?
正如其他地方所指出的,MSVC和ICC按预期使用部分专用化;clang选择部分专用化,但弄乱了其类型参数,别名T2
short
而不是int
;gcc完全忽略了部分专用化。
为什么允许类外隐式实例化类模板成员类模板部分专用化
简单地说,允许其他形式的类模板成员类模板定义的语言都不排除类外隐式实例化的类模板成员类模板部分专用化。在 [temp.mem] 中,我们有:
1 - 可以在类或类模板中声明模板;此类模板称为成员模板。一个 成员模板可以在其类定义或类模板定义内部或外部定义。[...]
类模板部分专用化是模板声明 ([temp.class.spec]/1(。在同一段落中,有一个类外非专用类模板成员类模板部分专用化的示例 ([temp.class.spec]/5(:
template<class T> struct A {
struct C {
template<class T2> struct B { };
};
};
// partial specialization of A<T>::C::B<T2>
template<class T> template<class T2>
struct A<T>::C::B<T2*> { };
A<short>::C::B<int*> absip; // uses partial specialization
此处没有任何内容表明封闭作用域不能是封闭类模板的隐式专用化。
类似地,也有类内类模板成员类模板部分专用化和类外隐式实例化类模板成员类模板完全专用化 ([temp.class.spec.mfunc]/2( 的示例:
template<class T> struct A {
template<class T2> struct B {}; // #1
template<class T2> struct B<T2*> {}; // #2
};
template<> template<class T2> struct A<short>::B {}; // #3
A<char>::B<int*> abcip; // uses #2
A<short>::B<int*> absip; // uses #3
A<char>::B<int> abci; // uses #1
(clang(截至 3.7.0-svn235195(弄错了第二个示例;它选择 #2 而不是 #3 作为absip
。
虽然这没有明确提到类外隐式实例化的类模板成员类模板部分专用化,但它也不排除它;它不在这里的原因是它与所提出的特定点无关,即关于为特定专用化考虑哪些主模板或部分模板专用化。
根据 [temp.class.spec]:
6 - [...] 当主要时 使用模板名称,主模板的任何先前声明的部分专用化也使用。 考虑。
在上面的最小示例中,A<short>::B<T2*>
是主模板A<short>::B
的部分专用化,因此应考虑。
为什么可能不允许这样做
在其他讨论中,我们看到提到(封闭类模板的(隐式实例化可能导致主模板专用化定义的隐式实例化发生,从而导致格式错误的程序 NDR,即 UB;[templ.expl.spec]:
6 - 如果模板、成员模板或类模板的成员明确专用,则该专用化 应在首次使用该专用化之前声明,这将导致隐式实例化 在发生这种使用的每个翻译单元中进行;无需诊断。[...]
但是,在实例化类模板之前,此处不使用类模板成员类模板。
其他人怎么想
在DR1755(活动(中,给出的示例为:
template<typename A> struct X { template<typename B> struct Y; };
template struct X<int>;
template<typename A> template<typename B> struct X<A>::Y<B*> { int n; };
int k = X<int>::Y<int*>().n;
仅从实例化封闭类的第二行存在的角度来看,这才被认为是有问题的。提交者(理查德·史密斯(或CWG没有暗示即使没有第二行也可能无效。
在 n4090 中,给出的示例为:
template<class T> struct A {
template<class U> struct B {int i; }; // #0
template<> struct B<float**> {int i2; }; // #1
// ...
};
// ...
template<> template<class U> // #6
struct A<char>::B<U*>{ int m; };
// ...
int a2 = A<char>::B<float**>{}.m; // Use #6 Not #1
这里提出的问题是类内类模板成员类模板完全专用化和类外类模板实例化成员类模板部分专用化之间的优先级;没有迹象表明根本不会考虑#6
。
- 为什么 Clang 和 GCC 中两个无符号整数之和的结果类型不同
- 使用具有默认参数的函数模板进行 decltype 会使结果混乱(一个有趣的问题或 gcc 的错误)
- 转换为标准时,Clang和GCC中的不同结果::可选<T>
- 为什么 std::locale( " " ).name() 在 clang 和 gcc 上给出不同的结果?
- 在这种情况下,GCC 和 clang 是否显示与 Visual Studio 相同的结果,关于语言链接?
- 未经GCC优化而编译的简单C++程序不会产生预期的结果
- 菱形层次结构中的虚函数重载在 clang 和 gcc 中产生不同的结果
- GCC 9.1 返回 void& 作为显式析构函数调用的结果类型。这是一个错误吗?
- 为什么 GCC 调用 libc 的 sqrt() 而不使用其结果?
- 使用 constexpr 函数的结果作为模板参数(clang vs gcc)
- 相同的AVX2程序在gcc和msvc中产生不同的结果
- 为什么 gcc 和 clang 为我的程序编译为不同的"const"结果?
- 用不同版本的Ubuntu上的GCC编译会产生不同的结果
- GCC:为什么编译.HPP文件与.cpp文件的结果不同
- 在 GCC 4.6 和 4.7 上模板模板扣除的两个不同结果
- 为什么不同的 GCC 4.9.2 安装为此正则表达式匹配给出不同的结果
- gdb中的C++static_cast返回的结果与gcc不同
- 编译一个相当简单的c++11程序时,gcc和clang之间的结果不同
- 将 C++11 正则表达式与 gcc 4.8.2 一起使用时会出现奇怪的结果(但适用于 Boost 正则表达式)
- 自分配中的后增量,VS2013和GCC之间的结果不同