部分模板特化问题

时间:2014-06-09 18:32:04

标签: c++ templates c++11 template-specialization partial-specialization

我有以下类结构

// 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模板上。

2 个答案:

答案 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}}

注意:主要缺点是您可能需要为层次结构中的所有类提供默认构造函数。这可能是也可能不是您可以克服的障碍。大多数类已经有一个默认的构造函数,但有些可能没有。从这个意义上来说,这个解决方案是侵入性的(即它不能在现有代码的基础上固定,但它需要修改该代码)。