奇怪的嵌套类部分特化结果在 gcc 和 clung 上

Weird nested class partial specialization results on both gcc and clang

本文关键字:结果 gcc clung 嵌套 类部      更新时间:2023-10-16

在编写一个供个人使用的小型模板元编程库时,我遇到了一个有趣的问题。

由于我为某些元函数重用了一些部分专用化,因此我决定将它们放在一个通用模板类下,并使用标签和嵌套的部分专用化来提供行为差异。

问题是我得到了荒谬的(对我来说(结果。这是一个最小的示例,展示了我正在尝试做的事情:

#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

相关文章: