我有以下类结构
// 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
。
注意应解决歧义,以便为任何bar.h
挑选文件aux<bar<T>>
中提供的专业化。最初,bar
不是模板,专业化aux<bar>
不是部分的,因此是首选。问题出现在bar
模板上。
答案 0 :(得分:4)
由于第二个模板参数,编译器看不到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更专业。
答案 1 :(得分:2)
部分专业化基于模式匹配。相比之下,自定义功能模板基于模板参数推断和重载决策。
由于您的问题中存在类层次结构,原则上通过函数模板重载更方便自定义行为,因为这可以采用派生到基础转换考虑到了。部分类模板专业化中使用的模式匹配不提供相同的灵活性。
但是,从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
}
Live Example在C ++ 11模式下同时适用于g ++和clang(不需要C ++ 14模式!)。
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 }
{{1}}
注意:主要缺点是您可能需要为层次结构中的所有类提供默认构造函数。这可能是也可能不是您可以克服的障碍。大多数类已经有一个默认的构造函数,但有些可能没有。从这个意义上来说,这个解决方案是侵入性的(即它不能在现有代码的基础上固定,但它需要修改该代码)。