gcc和clang上都有奇怪的嵌套类部分特化结果

Ver*_*tas 24 c++ templates language-lawyer template-meta-programming c++11

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

由于我正在为一些元函数重用一些部分特化,我决定将它们放在一个公共模板类下,并使用标签和嵌套的部分特化来提供行为上的差异.

问题是我得到了荒谬(对我而言)的结果.这是一个最小的例子,展示了我想要做的事情:

#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;
}
Run Code Online (Sandbox Code Playgroud)

vargs<char, int>当使用5.1.0版本的gcc和tag使用3.6.0版本的clang 时,我得到了输出.我的目的是打印上面的代码,char所以我对这些结果感到非常困惑.

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

eca*_*mur 8

你的代码是正确的; 类外隐式实例化的类模板成员类模板只要它们足够早地定义,标准就允许部分特化.

首先,让我们尝试一个最小的例子 - 注意这里没有什么需要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?
Run Code Online (Sandbox Code Playgroud)

如其他地方所述,MSVC和ICC按预期使用部分专业化; 铛选择部分特但弄乱其类型参数,混叠T2short代替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
Run Code Online (Sandbox Code Playgroud)

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

类似地,有类内模板成员类模板部分特化和类外隐式实例化类模板成员类模板完全特化([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
Run Code Online (Sandbox Code Playgroud)

(clang(从3.7.0-svn235195开始)得到第二个例子错误;它选择#2而不是#3 absip.)

虽然这没有明确提到类外隐式实例化的类模板成员类模板部分特化,但它也不排除它; 它不在这里的原因是它与特定点无关,这与特定专业化考虑哪些主要模板或部分模板特化.

Per [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;
Run Code Online (Sandbox Code Playgroud)

仅从存在实例化封闭类的第二行的观点来看,这被认为是有问题的.提交者(理查德史密斯)或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
Run Code Online (Sandbox Code Playgroud)

这里提出的问题是在类内模板成员类模板完全特化和类外模板实例化成员类模板部分特化之间的优先级; 没有任何建议#6根本不予考虑.

  • 我的印象非常明确,你写了一个非常好的答案,但所有这些非常长的短语(如"课外隐式实例化的类模板成员类模板部分专业化")的出现阻止了我虚弱的人类大脑解析并理解你的答案.有没有办法让文本更容易访问?您是否可以从一些解释清楚的定义开始,然后在帖子的其余部分使用缩写? (4认同)