繁体   English   中英

类模板的单个成员可以部分特化吗?

[英]Can a single member of a class template be partially specialized?

我遇到了一个有趣的问题,我无法解释或找到解释。 考虑以下模板定义(使用 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()
{
}

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

部分特化的概念只存在于类模板(由 §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 节) 类模板的成员或成员模板可以为类模板的给定隐式实例显式特化,即使该成员或成员模板是在类模板定义中定义的。 [...]

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

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

让我们暂时忘记模板。 在 C++ 中,类名和函数名之间存在很大差异。 在一个给定的范围内,一个类只能有一个定义。 (您可以有各种声明,但它们都指的是 One True Class。)所以名称真正标识了该类。

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

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

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

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

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

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

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

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

希望有帮助...

我认为不同之处在于,当您对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() 
     ^

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM