【问题标题】:Why is partial specialization of a nested class template allowed, while complete isn't?为什么允许嵌套类模板的部分特化,而不允许完全特化?
【发布时间】:2011-02-02 00:32:39
【问题描述】:
    template<int x> struct A {                                                                                                    
        template<int y> struct B {};.                                                                                             
        template<int y, int unused> struct C {};                                                                                  
    };                                                                                                                            

    template<int x> template<> 
    struct A<x>::B<x> {}; // error: enclosing class templates are not explicitly specialized

    template<int x> template<int unused> 
    struct A<x>::C<x, unused> {}; // ok

那么,如果外部类也没有特化,为什么不允许内部嵌套类(或函数)的显式特化呢?奇怪的是,如果我只部分通过简单地添加一个虚拟模板参数来专门化内部类,我就可以解决这个问题。让事情变得更丑陋和更复杂,但它确实有效。

我会将完全特化视为部分特化的子集 - 特别是因为您可以通过添加虚拟参数将每个完整特化表示为部分特化。所以这种部分专业化和完全专业化之间的歧义对我来说真的没有意义。

不幸的是,comp.std.c++ 中没有人敢回答,所以我再次将它放在这里。

注意:对于一组外部类的内部类的递归模板,我需要此功能,并且内部参数的特殊化确实取决于外部模板参数。

【问题讨论】:

标签: c++ templates metaprogramming


【解决方案1】:

我对为什么会发生这种情况的猜测:完整的特化不再是“模板类/函数”,它们是“真实的”类/方法,并且可以拥有真实的(链接器可见的)符号。但是对于部分专业化模板中的完全专业化模板,这是不正确的。 可能做出这个决定只是为了简化编译器编写者的生活(并且在此过程中让编码人员的生活更加困难:P)。

【讨论】:

  • 这似乎是一个很好的理由。但是 OTOH,编译器编写者难道不能像我一样添加一个不可见的、未使用的虚拟参数吗?
  • 这就是我在括号中开这个玩笑的原因。很明显,没有像“它永远无法工作,因为 [...]”这样的硬性理由,所以它被禁止的最好理由是“不要混淆用户”。但这会很有趣,考虑到 n00b C++ 程序员可能会以多少其他方式感到困惑 :)。实际上,我发现 C++ 在混淆毫无戒心的新手和阻止非常高级的程序员进行有效(有时甚至是优雅)的工作方面做得非常出色,所有这些都是为了明显微小的实现细节。但这只是我的看法,当然:)
  • 公平地说,还有其他一些你没有考虑过的含义。假设你定义了 A ,编译器添加了一个额外的虚拟参数。但由于它是一个模板,它被包含在多个 .cpp 文件中(但不是全部!)。假设您最终处于在文件 x.cc 和 y.cc 中使用需要虚拟参数的 A 版本的情况;而在文件 z.cc 中,您最终会使用完全不同的 A 版本 - 这是一个模板,但从一开始就有两个类型参数(并且不需要虚拟)。您如何分辨链接时的差异?
  • 叹息......模板类中嵌套模板事物的完全特化实际上只是部分特化吗?有时我很感激我大部分时间都在使用 VC++。嗯,有时我觉得我不想再使用 C++ XD
  • 我用我毕生的积蓄投票反对做出此决定的 C++ 标准委员会。是时候将你的职业生涯赌在更好的语言上了。
【解决方案2】:

C++ 标准在第一种情况下明确禁止成员模板类的完全特化。根据 14.7.3/18:

在类模板或成员模板的成员的显式特化声明中出现 在命名空间范围内,成员模板和它的一些封闭类模板可能保持非特化, 除了声明 不应显式特化一个类成员模板,如果它的封闭类 模板也没有明确专门化

【讨论】:

  • 这不是我的问题的答案。如果您阅读了我的问题,您会注意到我已经知道嵌套类的显式特化是不允许的。我已经找到了规避这个禁令的方法。我的问题是为什么会这样?
  • 只有 C++ 标准的创造者才真正知道为什么会这样。
  • “只有 C++ 标准的创造者才真正知道为什么会这样”——是什么让你这么说? C++ 标准是通过一个开放的过程开发的,许多决定都是公开辩论的:在邮件列表或新闻组上,在会议上。此外,Stroustrup 还写了一本关于 C++ 背后的一些设计决策的书。您是否检查过没有关于此特定决定的可用信息,或者这是关于任何人都可以“真正了解”任何事情的哲学论证? ;-) 语言设计不是“专家”走进去,标准出来的黑盒子。
  • @Steve,“C++ 标准是通过一个开放的过程开发的”
  • @litb: 是的,如果早期 Andrew Koenig 和 Bjarne Stroustrup 在电梯中相遇,而且没有人花几分钟时间,那么谁知道可能做了什么黑暗交易。我的观点并不是说 C++ 是完全透明的,只是说基里尔的绝对声明是极其没有野心的,考虑到在许多情况下,与 C++ 有关的决定已经在公开的广告中播出,令人作呕。我不知道这是否是其中之一,我只是厌倦了语言设计是程序员没有必要去思考或好奇的想法。 “重返工作岗位,劳动单位 drhirsch” ;-)
【解决方案3】:

您可以通过将实际工作委托给另一个结构来解决此问题:

namespace detail
{
  template <class T, class U>
  struct InnerImpl {};
}

template <class T>
struct Outer
{
  template <class U>
  struct Inner: detail::InnerImpl<T,U>
  {
  };
};

现在你可以随心所欲地专业化InnerImpl

【讨论】:

  • 有趣的想法。但是它对我有用吗,因为我需要内部类的特化依赖于外部类的模板参数?
  • 是的:如您所见,我将T 传递给InnerImpl 模板类。
  • 我想知道这是否比使用虚拟模板参数更好。
  • @FabioA.:好问题。我更喜欢尽可能避免使用虚拟参数或参数,因为它们在 API 中公开。即使命名得当,它们仍然会使它杂乱无章。但是我从根本上并不反对使用虚拟参数,这更像是一种替代解决方案:)
【解决方案4】:

支持 Virgil 的论点(他比我发布相同的理由更快),考虑一下:

template<typename T1>
class TOuter
  {
  public:
    template<typename T2>
    class TInner
      {
      public:
        T1 m_1;
        T2 m_2;
      };
  };

template<typename T1>
template<>
class TOuter<T1>::TInner<float>
  {
  public:
    T1    m_1;
    float m_2;
 };

由于 T1,TInner 是完全特化还是部分特化?

编辑:

在考虑了其他一些 cmets 之后 - 似乎您希望基于外部类中的模板参数进行完全专业化。如果嵌套内部类的实现,这似乎在 Visual Studio 2005 中有效:

template<typename T1>
class TOuter
  {
  public:
    template<typename T2>
    class TInner
      {
      public:
        std::string DoSomething() { return "Inner - general"; }
        T2 m_2;
      };

    template<>
    class TInner<T1>
      {
      public:
        std::string DoSomething() { return "Inner - special"; }
        T1 m_1;
      };
  };

TOuter::TInner 将正确地成为 TInner 的特化。我无法让它与模板之外的实现一起编译。

【讨论】:

  • 我认为,TOuter::TInner 是部分特化的,而 TInner 是完全特化的,但类型不完整;-)
  • 并假设您在 TOuter::TInner 中犯了“语义错误”(或仅引用不存在的符号) - 如果您收到编译/链接器错误,或者不是? ;)(考虑到 TOuter 可能永远不会被使用......)
  • 我在真实代码中就遇到过这种情况——专用内部类中的构造函数类型错误。 (我正在使用我在开头描述的部分特化规避。)只有在声明了 TOuter 类型的变量时才会出现错误。
  • 并且在某些成员函数中使用了 TInner 类型的变量...无论如何,我希望编译器能够使用虚拟参数神奇地扩展模板参数列表,这样我就不需要做它;-)
  • 关于您的编辑:是的,专业化取决于外部模板的参数。实际上,您的代码是我尝试的第一种方式-但是 gcc 拒绝使用“错误:非命名空间范围'class TOuter '中的显式专业化”来翻译它,如果我理解标准中的14.7.3.2,这可能是正确的正确。无论如何,MSVC 似乎允许这些专业化。
猜你喜欢
  • 1970-01-01
  • 2013-09-05
  • 1970-01-01
  • 1970-01-01
  • 2019-05-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-01
相关资源
最近更新 更多