9

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

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

如果我们愿意,我们可以完全专门化任何单个成员函数:

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

但部分特化失败,出现“无效使用不完整类型 'class Foo<...>'”错误:

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

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

我不知道为什么。这是为了避免一些我无法预见的问题而做出的有意识的设计决定吗?是疏忽吗?

4

3 回答 3

7

部分特化的概念只存在于类模板(由§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)以下任何一项的显式特化:
[...]
— 类模板的成员函数
[...]
可以通过 template<> 引入的声明来声明;[...]

§14.7.3/14 描述了细节:

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

因此,对于成员的显式特化,类模板其余部分的实例化是隐式工作的——它派生自主模板定义,或任何部分特化(如果已定义)。

于 2012-12-18T02:02:57.363 回答
5

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

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

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

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

模板类的名称仍然是唯一的。尽管您可以定义部分特化,但您必须显式特化同一个模板类。这种机制表面上看起来像上面提到的函数名解析算法,但有很大的不同——其中之一是,与函数原型不同,您不能在同一范围内拥有两个具有不同类型模板参数的类模板。

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

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

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

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

通常,这实际上足以满足您的重载需求。您应该像考虑普通函数一样考虑模板化函数:想出一种方法来根据提供的参数选择所需的函数。如果你觉得你真的需要在函数调用中提供模板参数,而不是让它们被推导,只需使函数成为模板类的(可能是静态的)成员,并将模板参数提供给类。

希望有帮助...

于 2012-12-18T01:13:26.707 回答
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>’

Withclang++更清楚一点:

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() 
     ^
于 2012-12-17T23:09:59.507 回答