我刚刚编写了以下代码
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_type
和value_type
typedef,该怎么办?编译器将如何选择使用哪个模板函数?
使用当前的STL地图,key_type
是pair
,但如果我定义key_type
与value_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::set
有key_type == value_type
。所以我真的需要求助于模板元编程才能拥有一个简单的contains
函数。 叹息
引用标准的加分点。
答案 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_type
是pair<const int,int>
- 因此只有您的第一个模板才匹配。如果您的第二个模板使用, mapped_type
由于含糊不清,你会遇到编译器错误。
答案 2 :(得分:2)
如果有两个或更多同样适合函数模板特化的候选者,则会出现编译器错误。
如果只有一个可行的功能比一个更好的功能 所有其他可行的功能,然后它是由重载选择的功能 解析度;否则电话会形成不良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)
- 例子]