gcc 和 clang 的奇怪嵌套 class 部分专业化结果

Weird nested class partial specialization results on both gcc and clang

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

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

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

#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 所以我对这些结果感到很困惑。

上面的代码是合法的还是表现出未定义的行为? 如果合法,根据标准预期的行为是什么?

您的代码是正确的; class 隐式实例化 class 模板成员 class 模板偏特化旨在被标准允许,只要它们定义得足够早。

首先,让我们尝试一个最小的例子 - 顺便注意这里没有任何东西需要 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 完全忽略了部分专业化。

为什么 class 隐式实例化 class 模板成员 class 模板偏特化是允许的

简单地说,none允许其他形式的class模板成员class模板定义的语言排除了class隐式实例化class 模板成员 class 模板偏特化。在 [temp.mem] 中,我们有:

1 - A template can be declared within a class or class template; such a template is called a member template. A member template can be defined within or outside its class definition or class template definition. [...]

A class 模板偏特化是模板声明 ([temp.class.spec]/1)。在同一段中,有一个非class非特化class模板成员class模板偏特化的例子([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

这里没有任何内容表明封闭范围不能是封闭 class 模板的隐式特化。

同样,还有in-class class模板成员class模板偏特化和out-class隐式实例化的例子class模板成员 class 模板完全专业化 ([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 起)将第二个示例弄错;它为 absip 选择了 #2 而不是 #3。)

虽然这没有明确提到class隐式实例化class模板成员class模板偏特化,但也不排除它;它不在这里的原因是它与所提出的特定观点无关,即关于哪个主模板或部分模板专业化被认为是特定专业化。

根据 [temp.class.spec]:

6 - [...] when the primary template name is used, any previously-declared partial specializations of the primary template are also considered.

在上面的最小示例中,A<short>::B<T2*> 是主模板 A<short>::B 的部分特化,因此应予以考虑。

为什么不允许

在其他讨论中,我们看到提到隐式实例化(封闭的 class 模板)可能导致主模板特化定义的隐式实例化发生,从而导致格式错误程序 NDR 即 U​​B; [templ.expl.spec]:

6 - If a template, a member template or a member of a class template is explicitly specialized then that specialization shall be declared before the first use of that specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required. [...]

但是,这里class模板成员class模板在实例化之前没有使用。

别人怎么想

DR1755 (active)中给出的例子是:

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;

仅从实例化封闭 class 的第二行的存在的角度来看,这才被认为是有问题的。提交者 (Richard Smith) 或 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

这里提出的问题是 in-class class 模板成员 class 模板全特化和 out-of-class [=87] 之间的优先级=] 模板实例化成员 class 模板偏特化;没有人说 #6 根本不会被考虑。