【问题标题】:Weird nested class partial specialization results on both gcc and clanggcc 和 clang 上奇怪的嵌套类部分专业化结果
【发布时间】:2015-10-02 07:36:51
【问题描述】:

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

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

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

#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&lt;char, int&gt; 作为输出,当使用 3.6.0 版本的 clang 时,我得到 tag。我的意图是让上面的代码打印char,所以我对这些结果感到很困惑。

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

【问题讨论】:

  • 似乎 Clang 在某个过程中提出了details::outer&lt;tag&gt;::inner&lt;tag&gt;。有趣,因为我不知道编译器如何混淆这些模板参数。
  • 轻微修改会导致 Clang 中的 ICE:coliru.stacked-crooked.com/a/5ae1c222d3d9ad30
  • 有趣的是,如果你完全用 inner 的默认和特殊形式专门化 outer&lt;tag&gt;,它会产生预期的输出:Coliru
  • VS 2015 打印 char.
  • 与@jaggedSpire 类似的一个稍微简单但同样有效的更改是在尝试对其进行专门化之前为outer&lt;tag&gt;::inner 添加定义。请参阅Ideone

标签: c++ templates c++11 language-lawyer template-meta-programming


【解决方案1】:

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

首先,让我们尝试一个最小的示例 - 顺便注意这里没有任何东西需要 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) 得到第二个示例错误;它为 absip 选择 #2 而不是 #3。)

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

根据[temp.class.spec]

6 - [...] 当主 使用模板名称,任何先前声明的主模板的部分特化也是 考虑。

在上述最小示例中,A&lt;short&gt;::B&lt;T2*&gt; 是主模板 A&lt;short&gt;::B 的部分特化,因此应予以考虑。

为什么不允许这样做

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

6 - 如果模板、成员模板或类模板的成员被显式特化,则该特化 应在第一次使用会导致隐式实例化的特化之前声明 在发生这种使用的每个翻译单元中进行;不需要诊断。 [...]

但是,这里的类模板成员类模板在实例化之前是没有用到的。

其他人的想法

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;

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

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

【讨论】:

  • 我的印象是你写了一个非常好的答案,但是所有这些很长的短语的存在(比如“类外隐式实例化的类模板成员类模板部分专业化” ) 防止我虚弱的人脑完全解析和理解您的答案。有没有办法让文本更容易理解?您能否从一些解释清楚的定义开始,然后在文章的其余部分使用缩写词?
  • @Julian,我也会很感激这样的事情。根据我自己的实验,我的直觉是,在提供(或至少声明)非专业模板之前,您不能专门化模板,这取决于一般规则。编译器并没有完全将最后一个模板代码识别为特化,也不承认第一个模板代码提供了非特化 inner...(待续)...
  • ..(续).. 我认为这是相关的,因为如果我添加几行(this on ideone 的第 32-37 行),那么两个编译器都会正常运行;这些行提供了一个非专业的outer&lt;tag&gt;::inner。 (嗯,很明显,outer 专门用于 outer&lt;tag&gt; - 但 inner 本身并不专门用于此)。我认为这有助于两个编译器理解模板是特化的。无论如何,也许我的直觉是错误的!
  • @AaronMcDaid 你正在做的是重新定义类模板outer&lt;tag&gt;::inner(作为实例化outer&lt;tag&gt;的成员);但是隐式实例化outer&lt;tag&gt; 的行为应该(根据[temp.inst])导致outer&lt;tag&gt;::inner 的声明,这使其可用于部分特化。请注意,clang 选择了部分特化,它只是对其模板参数感到困惑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-18
  • 1970-01-01
  • 2015-08-21
  • 1970-01-01
相关资源
最近更新 更多