模板类不完全专业化

时间:2012-12-17 22:59:24

标签: c++ template-specialization

我遇到了一个有趣的观点,我无法解释或找到解释。考虑以下模板定义(使用mingw g ++ 4.6.2编译):

template <typename T, typename S>
class Foo
{
public:
    void f(){}
    void g(){}
};

如果我们想要,我们可以完全专门化任何单一成员函数:

template <>
void Foo<char,int>::f() {}

但部分专业化失败,“无效使用不完整类型'类Foo&lt; ...&gt;'”错误:

template <typename T, typename S>
void Foo<T,S*>::f()
{
}

template <typename T>
void Foo<T,int>::f()
{
}

我无法弄清楚原因。这是一个有意识的设计决策,以避免一些我无法预见的问题?这是疏忽吗?

提前致谢。

3 个答案:

答案 0 :(得分:6)

部分特化的概念仅存在于类模板(由§14.5.5描述)和成员模板(即模板类的成员本身是模板函数,由§14.5.5.3描述) / 2)。它不存在于类模板的普通成员中,也不存在于函数模板中 - 仅仅因为它没有被标准描述。

现在,您可能会认为通过给出成员函数的部分特化的定义,例如

template <typename T>
void Foo<T,int>::f()
{ }

隐式定义类模板的部分特化:Foo<T,int>。但是, 明确排除了标准:

  

(§14.5.5/ 2)每个类模板部分特化是一个不同的模板,应为模板部分特化的成员提供定义(14.5.5.3)。

     

(§14.5.5.3/ 1)[...]类模板部分特化的成员与主模板的成员无关。应定义以需要定义的方式使用的类模板部分特化成员;主模板成员的定义从不用作类模板部分特化的成员的定义。 [...]

后者暗示通过简单地给出其成员之一的定义隐式地定义部分特化是不可能的:该成员的存在不会从主模板的定义中得出因此定义它等同于定义一个不是声明的成员函数,并且这是不允许的(即使是非模板类)。

另一方面,类模板的成员函数存在显式特化(或完全特化,如您所说)的概念。它由标准明确描述:

  

(§14.7.3/ 1)以下任何一项的明确专业化:
  [...]
   - 类模板的成员函数
  [...]
  可以通过模板&lt;&gt;引入的声明声明; [...]

§14.7.3/ 14描述了细节:

  

(§14.7.3/ 14)类模板的成员或成员模板可以显式专用于类模板的给定隐式实例化,即使在类模板定义中定义了成员或成员模板也是如此。 [...]

因此,对于成员的显式特化,类模板的其余部分的实例化是隐式工作的 - 它是从主模板定义派生的,或者是定义的任何部分特化。

答案 1 :(得分:5)

我试图从标准中找到一个简洁的引用,但我不认为有一个。事实是,没有模板函数的部分特化(或者,就此而言,模板别名)。只有类模板可以具有部分特化。

让我们忘记模板一秒钟。在C ++中,类名和函数名之间存在很大差异。给定范围内只能有一个类的定义。 (你可以有各种声明,但它们都引用了One True Class。)所以这个名字确实标识了这个类。

另一方面,函数名称是一种组标识。您可以使用完全相同的名称在范围内定义任意数量的函数。当您使用函数名称来调用函数时,编译器必须通过查看各种可能性并使用提供的参数匹配每个函数的签名来确定您真正意味着哪个函数。共享名称的各种功能之间没有关系;他们是完全独立的实体。

所以,没什么大不了的。你知道这一切,对吧?但现在让我们回到模板。

模板类的名称仍然是唯一的。虽然您可以定义部分特化,但您必须明确专门化相同的模板化类。这种机制看起来像上面提到的函数名称解析算法,但存在显着差异 - 其中之一就是,与函数原型不同,您不能在同一范围内使用不同类型的模板参数创建两个类模板。 / p>

另一方面,模板化函数无需定义唯一名称。模板化不会取代正常的功能过载机制。因此,当编译器试图找出函数名称的含义时,它必须考虑该函数名称的所有模板化和非模板化声明,将模板化的声明解析为一组模板参数赋值(如果可能)然后一旦它有一个可能的函数对象列表,选择具有正常重载分辨率的最佳函数对象。

这与模板化的类模板参数分辨率完全不同。它不是仅仅将提供的模板参数列表与声明的模板参数列表进行匹配,而是解析类模板的方式,而是必须采用可能匹配的每个模板化函数(例如,至少具有正确数量的参数) ;通过将提供的参数与模板统一来推导模板参数;然后将解析特化添加到重载集以进行进一步的重载解析。

我认为也可以在该过程中添加部分特化解决方案,但是部分特化和函数重载之间的相互作用可能会导致伪魔法行为。在这种情况下,没有必要,因此没有这样的机制。 (你可以完全专门化一个函数模板。完全特化意味着没有模板参数可以推导出来,所以这不是问题。)

所以这就是独家新闻:你不能部分地专门化一个模板化的函数,但没有什么可以阻止你提供任意数量的具有相同名称的函数模板。所有这些都将在重载决议中被考虑,并且最好的将像往常一样获胜。

通常,这实际上足以满足您的超载需求。你应该像想象普通函数一样考虑模板化函数:想出一种基于提供的参数选择你想要的函数的方法。如果您认为您确实需要在函数调用中提供模板参数,而不是推导它们,只需将该函数设置为模板化类的(可能是静态的)成员,并将模板参数提供给该类。

希望有帮助...

答案 2 :(得分:3)

我认为不同之处在于,当您对f执行第一次(有效)显式特化时:

template <>
void Foo<char,int>::f() {}

您正在进行Foo<char,int>的隐式实例化。但是当您尝试使用:

进行部分特化时
template <typename T>
void Foo<T,int>::f()
{
}

编译器需要在进行特化之前隐式实例化Foo<T,int>,但由于T,它不能这样做。它失败了。

您可以使用以下代码检查是否是这种情况:

template <typename T, typename S>
class Foo
{
public:
    void f(){}
    void g(){}
};


template <>
void Foo<char,int>::f() //line 11
{}

template <>
class Foo<char,int> //line 15
{};

使用g++会出错:

test.cpp:15:7: error: specialization of ‘Foo<char, int>’ after instantiation
test.cpp:15:7: error: redefinition of ‘class Foo<char, int>’
test.cpp:2:7: error: previous definition of ‘class Foo<char, int>’

clang++有点清楚:

test.cpp:15:7: error: explicit specialization of 'Foo<char, int>' after instantiation
class Foo<char,int>
      ^~~~~~~~~~~~~
test.cpp:11:6: note: implicit instantiation first required here
void Foo<char,int>::f() 
     ^