【问题标题】:Can a single member of a class template be partially specialized?类模板的单个成员可以部分特化吗?
【发布时间】:2012-12-17 22:59:23
【问题描述】:

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

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

【问题讨论】:

标签: c++ template-specialization


【解决方案1】:

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

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

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

隐式定义了类模板的部分特化:Foo&lt;T,int&gt;。然而, 被标准明确排除:

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

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

后者意味着不可能隐式通过简单地给出其成员之一的定义来定义部分特化:该成员的存在本身不会从主模板的定义中得出,因此定义它相当于定义一个未声明的成员函数,这是不允许的(即使是非模板类)。

另一方面,显式特化(或完全特化,你称之为)的概念存在于类模板的成员函数中。标准明确描述了它:

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

§14.7.3/14 描述了细节:

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

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

【讨论】:

  • 这就是我一直在寻找的东西。无法破译标准的相关部分。谢谢:)
【解决方案2】:

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

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

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

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

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

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

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

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

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

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

希望对您有所帮助...

【讨论】:

  • 它确实有帮助,事实上很多。谢谢:)
【解决方案3】:

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

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

您正在对Foo&lt;char,int&gt; 进行隐式实例化。但是当你尝试部分专业化时:

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

编译器需要在进行特化之前隐式实例化Foo&lt;T,int&gt;,但由于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() 
     ^

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多