C ++中模板选择的优先级

时间:2011-04-05 13:16:45

标签: c++ templates

我刚刚编写了以下代码

template<typename T>
inline bool contains(T haystack,typename T::key_type needle) {
    return haystack.find(needle) != haystack.end();
}

template<typename T>
inline bool contains(T haystack,typename T::value_type needle) {
    return find(haystack.begin(),haystack.end(),needle) != haystack.end();
}

当我使用vector实例化没有key_type typedef的模板时,SFINAE将确保我不会实例化第一个版本。但是,如果我使用map实例化模板,同时具有key_typevalue_type typedef,该怎么办?编译器将如何选择使用哪个模板函数?

使用当前的STL地图,key_typepair,但如果我定义key_typevalue_type相同的类型会怎样?

class MyMap {typedef int key_type;typedef int value_type;};
MyMap m;
contains(m,1); // both functions are valid, which will be chosen?

令人惊讶的是,std::setkey_type == value_type。所以我真的需要求助于模板元编程才能拥有一个简单的contains函数。 叹息

引用标准的加分点。

4 个答案:

答案 0 :(得分:3)

如果你愿意这样做,你可以通过一些元编程帮助你。基本上你需要编写一个模板元函数来确定你想要调用一个或另一个函数的条件,并在enable_if_c子句中使用它:

template <typename T> inline
typename enable_if_c< has_key_type<T>::value, bool >::type
contains( T const & c, T::key_type const & k ) {...}        // associative container

template <typename T> inline
typename enable_if_c< !has_key_type<T>::value, bool >::type
contains( T const & c, T::value_type const & k ) {...}      // non-associative container

enable_if_c模板是一个简单的常见SFINAE技巧(可以从C ++ 0x编译器或boost中使用)。它需要一个条件和一个类型,如果条件为真,它将为参数类型生成一个内部typedef,如果它不存在则不会定义该内部类型,并且可以在SFINAE外部使用:

template <bool condition, typename T>
struct enable_if_c {};        // by default do not declare inner type

template <typename T>
struct enable_if_c<true,T> {  // if true, typedef the argument as inner type
    typedef T type;
};

现在有趣的部分是如何确定类型T具有内部类型key_type,虽然可能还有其他选项,但首先想到的是:

template <typename T>
class has_key_type {
    typedef char _a;
    struct _b { _a x[2]; };

    template <typename U>
    static _a foo( U const &, typename U::key_type* p = 0 );
    static _b foo( ... );
    static T& generate_ref();
public:
    static const bool value = sizeof(foo(generate_ref())) == sizeof(_a);
};

模板has_key_type将包含内部常量value,仅当传递的int类型包含内部true类型时才为T::key_type。解决方案并不太复杂:定义一个模板函数,它对于所有类型都会失败,但是你想要检测的模板函数,并提供带有省略号的不同重载,以便在模板(具有比省略号更高的优先级)失败时捕获它替代。然后使用返回类型的大小来检测编译器选择的重载。 generate_ref是为了避免必须实际构造一个对象(即不要强加T可以以任何特定方式构造)。

总体结果是,当T类型包含内部类型key_type时,has_key_type<T>::value的结果将为true,而enable_if_c将启用第一个重载并禁用第二个,因此SFINAE将丢弃第二个重载,不是因为未定义类型第二个函数参数,而是因为返回类型的条款。

如果你想到它,只有一堆锅炉板代码围绕一个小的变化:而不是提供两个模糊的模板函数重载,提供模板重载和较低优先级的函数(省略号)。由于您无法真正使用该省略号函数来实现非关联容器版本,因此它用于获取布尔值,然后将其接种到常见的元编程结构和样板文件中。

答案 1 :(得分:2)

key_type模板是匹配,value_type模板不是。

map<int,int>::value_typepair<const int,int> - 因此只有您的第一个模板才匹配。如果您的第二个模板使用, mapped_type由于含糊不清,你会遇到编译器错误。

答案 2 :(得分:2)

如果有两个或更多同样适合函数模板特化的候选者,则会出现编译器错误。

C++ standard says:

  

如果只有一个可行的功能比一个更好的功能     所有其他可行的功能,然后它是由重载选择的功能     解析度;否则电话会形成不良12)。

答案 3 :(得分:0)

在地图上使用它似乎有效。

但是,如果你在某个容器上使用它,其中key_type和value_type是相同的类型,那么这最终会定义“相同”的函数两次(并且在此之上不同)。

§13.1

  

某些功能声明不能超载:
  ...
   - 仅在使用等效typedef“types”时不同的参数声明是等效的。一个   typedef不是单独的类型,而只是另一种类型的同义词(7.1.3)   [例子:
  typedef int Int;
  void f(int i);
  void f(Int i); // OK: redeclaration of f(int)
  void f(int i) { /* ... */ }
  void f(Int i) { /* ... */ } // error: redefinition of f(int)
   - 例子]