部分模板专门化带来的麻烦
trouble with partial template specialisations
我有以下的类结构
// file foo.h:
struct foo_base
{ ... }
template<typename T> struct foo : foo_base
{ ... };
template<typename F>
using is_foo = std::is_convertible<F,foo_base>;
template<typename, typename=void> struct aux;
template<typename Foo>
struct aux<Foo, typename std::enable_if<is_foo<Foo>::value>::type>
{ ... }; // specialisation for any foo
<一口>一口>
// file bar.h:
#include "foo.h"
template<typename T> struct bar : foo<T>
{ ... };
template<typename T>
struct aux<bar<T>>
{ ... }; // specialisation for bar<T>
现在,问题是对于aux<bar<T>>
,为aux
提供的两种专门化都是可行的。有没有一种方法可以避免这种歧义而不为每个T
提供另一种专门化?注意,对文件foo.h
的修改不能知道文件bar.h
。
注意歧义必须解决,以便为任何aux<bar<T>>
选择文件bar.h
中提供的专门化。最初,bar
并不是一个模板,而专门化aux<bar>
也不是局部的,因此是首选。当将bar
作为模板时,问题出现了。
由于第二个模板参数,编译器并不认为struct aux<bar<T>>
比struct aux<Foo, typename std::enable_if<is_foo<Foo>::value>::type>
更专门化。您可以在bar<T>
专门化中以相同的方式指定第二个参数:
template<typename T>
struct aux<bar<T>, typename std::enable_if<is_foo<bar<T>>::value>::type>
{ };
专门化部分模板的规则是复杂的,但我将尝试非常简单地解释:
三个(你的两个,加上我的一个)相关专业是
template<typename Foo>
struct aux<Foo, typename std::enable_if<is_foo<Foo>::value>::type>
template<typename T>
struct aux<bar<T>> // or aux<bar<T>, void>
{ };
template<typename T>
struct aux<bar<T>, typename std::enable_if<is_foo<bar<T>>::value>::type>
{ };
根据标准(14.5.5.2),要确定哪个类模板的部分特化是最特化的,需要回答的问题是以下哪个函数模板重载在调用f(aux<bar<T>>())
时将是最佳匹配:
template<typename Foo>
void f(aux<Foo, typename std::enable_if<is_foo<Foo>::value>::type>); // 1
template<typename T>
void f(aux<bar<T>>); // or aux<bar<T>, void> // 2
template<typename T>
void f(aux<bar<T>, typename std::enable_if<is_foo<bar<T>>::value>::type>); // 3
反过来,函数的偏序规则说1不比2更专门化,2也不比1更专门化,粗略地说,因为1不明显比2更专门化,而2也不明显比1更专门化。"明显更专业化"并不是标准的说法,但这实际上意味着基于其中一个的类型参数,另一个的类型参数是不可演绎的。
当比较1和3时,1的参数可以由3演绎出来:Foo
可以演绎为bar<T>
。因此,3至少和1一样专门化。然而,3的论点不能从1推导出来:T
根本不能推导出来。因此,编译器的结论是3比1更专门化。
类模板的局部专门化基于模式匹配。相比之下,自定义函数模板是基于模板实参演绎和重载解析。
由于问题中存在类层次结构,原则上通过函数模板重载可以更方便地定制行为,因为可以考虑派生到基的转换。在部分类模板专门化中使用的模式匹配不提供相同的灵活性。
但是,从c++ 11开始,可以进行编译时返回类型推断。下面是一个结合了标签调度、默认构造函数和decltype
类型推导的解决方案:
#include <iostream>
// file foo_base.h:
struct foo_base
{
foo_base() = default;
};
foo_base faux(foo_base const&)
{
return foo_base{};
}
template<class T, class = decltype(faux(T{}))>
struct aux;
template<class T>
struct aux<T, foo_base>
{
enum { value = 1 };
};
// file foo.h:
template<typename T>
struct foo : foo_base
{
foo() = default;
};
// file bar.h:
template<typename T>
struct bar : foo<T>
{
bar() = default;
};
template<class T>
bar<T> faux(bar<T> const&)
{
return bar<T>{};
}
template<class T, class U>
struct aux<T, bar<U>>
{
enum { value = 2 };
};
// file meow.h
template<class T>
struct meow : bar<T>
{
meow() = default;
};
int main()
{
std::cout << aux<foo_base>::value; // 1
std::cout << aux<foo<int>>::value; // 1
std::cout << aux<bar<int>>::value; // 2
std::cout << aux<meow<int>>::value; // 2
}
在c++ 11模式下(不需要c++ 14模式!)与g++和clang一起工作的实例
constexpr
函数faux()
被foo_bar
重载,并作为bar<T>
的函数模板。从foo_base
派生而不是从bar<T>
派生的任何参数将选择前一个重载,从bar<T>
派生的任何参数将选择后一个重载。该机制与标准库中的迭代器类别用于标签调度 std::advance()
的几个实现相同,例如
要在类模板aux
的部分专门化期间使用这种选择机制,还需要两个元素。首先,所有类都需要有一个默认构造函数。其次,将 decltype()
应用于表达式faux(T{})
,以推导出返回类型。
注意:不要求
faux()
是constexpr
,也不要求任何默认构造函数是constexpr
,因为decltype()
会不实际计算函数调用,而只是推断其返回值类型。
主类模板aux
有一个默认的模板参数:
template<class T, class = decltype(faux(T{}))>
struct aux;
部分专门化foo_base
的第二个参数允许您为任何从foo_base
派生的类提供行为:
template<class T>
struct aux<T, foo_base>
{ // custom behavior for anything derived from foo_bar };
第二个部分专门化匹配从任何模板实例化派生的任何类bar<U>
template<class T, class U>
struct aux<T, bar<U>>
{ // custom behavior for anything derived from bar<U> for some U }
注意:主要的缺点是您可能需要为层次结构中的所有类提供默认构造函数。这可能是你能克服的障碍,也可能不是。大多数类都有默认构造函数,但有些类可能没有。从这个意义上说,这个解决方案是侵入性的(即,它不能被固定在现有代码的顶部,但它需要修改该代码)。
- 是否可以对零模板参数进行模板专门化
- 尝试根据类中 typedef 的存在来专门化模板函数
- 我已经阅读了很多关于 2d 数组的信息,但我在作业中使用它时遇到了麻烦
- 如何基于模板化类的基类专门化成员函数
- 查找奇数平方和时遇到麻烦
- 如何为指向复杂值的迭代器专门化算法?
- 专门化模板覆盖函数/避免对象切片
- 我能否根据其运算符()的签名专门化可变参数模板参数
- 如何使用模板化类专门化模板化函数?
- 将双倍转换为 LPARAM 的麻烦
- 麻烦得到提升::记录器编译
- 线程 std::调用未知类型,无法专门化函数错误
- 翻转 ppm 图像C++麻烦
- 输入两个不专门化大小的矩阵
- 尝试使用 indexOf 创建一个 if 语句来检查字符串是否包含字符.有一点麻烦
- 搬家turtlebot_gazebo麻烦
- CSUF EPP - 你有什么想法(在添加到链接列表末尾时遇到麻烦)
- 如何在模板类中专门化赋值运算符?
- 如何专门化容器和枚举的模板
- 部分模板专门化带来的麻烦