如何在另一个类模板中定义完全特化类的构造函数

6
我有一个类模板,其中包含另一个类模板,内部模板具有显式特化:
template <typename Outer>
struct ContainingClass {
  template <typename T>
  struct Rule {
    Rule(T value);

    // ... other members ...
  };

  template <>
  struct Rule<void> {
    Rule();

    // ... different members than the non-void Rule<T> ...
  };
};

我已经为通用和专用的Rule定义了构造函数:
template <typename Outer>
template <typename T>
ContainingClass<Outer>::Rule<T>::Rule(T value) { }

template <typename Outer>
ContainingClass<Outer>::Rule<void>::Rule() { }

但是Clang不喜欢特殊类的构造函数:

error: nested name specifier 'ContainingClass<Outer>::Rule<void>::' for declaration does not refer into a class, class template or class template partial specialization
ContainingClass<Outer>::Rule<void>::Rule() { }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^

我对此感到困惑,因为它“引用”了ContainingClass<Outer>,而它是一个类(ContainingClass类模板的一个实例)。我怀疑我需要更改语法,但不清楚应该如何更改。如何定义这个构造函数?
(如果我删除ContainingClass并将Rule放在命名空间范围内,它可以工作,但我需要在Rule中有其他依赖于Outer类型的内容。我可以给Rule自己的Outer模板参数,但这会使使用这个类的代码更加麻烦,所以我希望尽可能避免。我知道我可以在Rule类体内内联定义构造函数,但我想了解为什么单独的定义不起作用。)

如果有影响的话,我正在Ubuntu 19.04上同时使用Clang 8.0和苹果的"clang-1001.0.46.4"。 (我还尝试过Ubuntu的GCC 8.3,但由于GCC bug #85282 - 在struct Rule<void>本身的定义中在非命名空间范围内显式特化 - 在不同的位置失败。)

编辑以澄清: 我的错误不在于在ContainingClass中有template <> struct Rule<void>特化。这是C++14限制的主题(defect CWG 727),通过添加虚拟模板参数来解决,使得模板只部分而不是完全特化。我相信这种限制已经在C++17中解除了,在Clang中,Rule类本身的特化工作正常(尽管GCC存在缺陷)。所以我认为虚拟参数解决方法在这里不是正确的解决方法-但请告诉我如果我错了,C++17中仍然存在这方面的限制。

可能是Nested class template specialization的重复问题。 - krisz
@krisz,这个限制在C++14中存在,但在C++17中已经被取消了,而且这不是我遇到的错误。(除了GCC因为我提到的错误而出现的问题。) - Wyzard
1个回答

5

您不能在命名空间范围内声明模板成员的特化成员。

为了避免这种限制,可以使用一个解决方法,这在 c++14 及之前版本中曾经是必要的,该方法是使用部分特化代替完全特化:

template <typename Outer>
struct ContainingClass {
  template <typename T,class=void>
  struct Rule {
    Rule(T value);

    // ... other members ...
  };

  template <class U>
  struct Rule<void,U> {
    Rule();

    // ... different members than the non-void Rule<T> ...
  };
};

template <typename Outer>
template <typename T, typename U>
ContainingClass<Outer>::Rule<T,U>::Rule(T value) { }

template <typename Outer>
template <typename U>
ContainingClass<Outer>::Rule<void,U>::Rule() { }

在C++17中,仍然不可能在命名空间范围内声明类模板的特化成员(成员),请参见[temp.expl.spec]/17。同一段落存在于C++14标准中。
C++17的变化在于,我们可以在封闭的类模板定义内声明成员的特化: 一个显式的特化应该在一个封装了特化模板的命名空间中声明。[...]

明示具体化可以在定义相应的主模板的任何作用域中声明。


与C++14的限制(以及GCC的错误)相似,即显式特化必须在命名空间范围而不是类范围内,因此它根本就不允许,在17中也是如此吗? - Wyzard
感谢您添加的参考资料 - 现在我明白了区别。 - Wyzard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接