简体   繁体   English

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

[英]Priority of template selection in C++

I just written the following piece of code 我刚写了下面这段代码

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();
}

When I'm instantiating the template with vector which doesn't have key_type typedef, SFINAE would make sure I wouldn't instantiate the first version. 当我使用没有key_type typedef的vector实例化模板时, SFINAE会确保我不会实例化第一个版本。 But what if I'm instantiating the template with a map , which have both key_type and value_type typedefs? 但是,如果我使用具有key_typevalue_type typedef的map实例化模板呢? How will the compiler choose which template function to use? 编译器如何选择使用哪个模板函数?

With current STL map, key_type is a pair , however what happens if I define a type where key_type is the same as value_type ? 使用当前的STL映射, key_type是一pair ,但是如果我定义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?

And surprise surprise, std::set has key_type == value_type . 令人惊讶的是, std::setkey_type == value_type So I really need to resort to template meta programming in order to have a simple contains function. 所以我真的需要求助于模板元编程才能拥有一个简单的contains函数。 Sigh . 叹了口气

Bonus point for quoting the standard. 引用标准的奖励点。

If you are willing to do it, you can, with some metaprogramming help to aid you. 如果你愿意这样做,你可以通过一些元编程帮助你。 Basically you would need to write a template metafunction that determines the condition under which you want to call one or the other functions, and use that in an enable_if_c clause: 基本上你需要编写一个模板元函数来确定你想要调用一个或另一个函数的条件,并在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

The enable_if_c template is a simple common SFINAE trick (that you can use from a C++0x compiler or boost). enable_if_c模板是一个简单的常见SFINAE技巧(可以从C ++ 0x编译器或boost中使用)。 It takes a condition and a type, if the condition is true, it will generate an inner typedef to the argument type, if it is not present it will not define that inner type, and that can be used outside in SFINAE: 它需要一个条件和一个类型,如果条件为真,它将为参数类型生成一个内部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;
};

Now the interesting part is how to determine that a type T has an inner type key_type , and while there might be other options, the first that comes to mind is: 现在有趣的部分是如何确定类型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);
};

The template has_key_type will contain an inner constant value that is true only if the type passed int contains an inner T::key_type type. 模板has_key_type将包含一个内部常value ,仅当传递的int类型包含内部T::key_type类型时才为true The resolutions is not too complex: define a template function that will fail for all types but the one you want to detect, and provide a different overload with ellipsis so that it will catch if the template (has higher priority than the ellipsis) fails to substitute. 解决方案并不太复杂:定义一个模板函数,它对于所有类型都会失败,但是你想要检测的模板函数,并提供带有省略号的不同重载,以便在模板(具有比省略号更高的优先级)失败时捕获它替代。 Then use the size of the return types to detect which overload was chosen by the compiler. 然后使用返回类型的大小来检测编译器选择的重载。 The generate_ref is there to avoid having to actually construct an object (ie do not impose that T can be constructed in any specific way). generate_ref是为了避免必须实际构造一个对象(即不强加T可以以任何特定方式构造)。

The overall result is that when the type T contains an inner type key_type , the result of has_key_type<T>::value will be true, and the enable_if_c will enable the first overload and disable the second, so SFINAE will discard the second overload not because of the type second function argument not being defined, but rather on terms of the return type. 总体结果是,当类型T包含内部类型key_typehas_key_type<T>::value将为true,并且enable_if_c将启用第一个重载并禁用第二个,因此SFINAE将丢弃第二个重载而不是因为没有定义类型的第二个函数参数,而是返回类型的术语。

If you think of it, there is just a bunch of boiler plate code around a small change: instead of providing two ambiguous template function overloads, provide a template overload and a lesser priority function (ellipsis). 如果你想到它,只有一堆锅炉板代码围绕一个小的变化:而不是提供两个模糊的模板函数重载,提供模板重载和较低优先级的功能(省略号)。 Since you cannot really use that ellipsis function to implement the non-associative container version, it is used to obtain a boolean value that is then seeded into common metaprogramming structures and boilerplate. 由于您无法真正使用该省略号函数来实现非关联容器版本,因此它用于获取布尔值,然后将其接种到公共元编程结构和样板文件中。

The key_type template is a match, the value_type template isn't. key_type模板是匹配的, value_type模板不是。

map<int,int>::value_type is a pair<const int,int> - so only your first template will match. map<int,int>::value_type是一pair<const int,int> - 所以只有你的第一个模板匹配。 If your second template used eg mapped_type you'd have a compiler error due to ambiguity. 如果您的第二个模板使用例如mapped_type ,则由于模糊性而导致编译器错误。

If there are two or more equally good candidates for function template specialization, you will get a compiler error. 如果有两个或更多同样适合函数模板特化的候选者,则会出现编译器错误。

C++ standard says: C ++标准说:

If there is exactly one viable function that is a better function than all other viable functions, then it is the one selected by overload resolution; 如果只有一个可行的函数比所有其他可行函数更好的函数,则它是由重载决策选择的函数; otherwise the call is ill-formed12). 否则电话会形成不良12)。

Using it on a map seems to work. 在地图上使用它似乎工作。

However, if you were to use it on some container where key_type and value_type were the same type, this would end up defining "the same" function twice (and differently on top of that). 但是,如果你在某个容器上使用它,其中key_type和value_type是相同的类型,这最终会定义“相同”的函数两次(并且在此之上不同)。

§13.1 §13.1

Certain function declarations cannot be overloaded: 某些函数声明不能​​重载:
... ...
— Parameter declarations that differ only in the use of equivalent typedef “types” are equivalent. - 仅在使用等效typedef“types”时不同的参数声明是等效的。 A typedef is not a separate type, but only a synonym for another type (7.1.3). typedef不是单独的类型,而只是另一种类型的同义词(7.1.3)。
[ Example: [例如:
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)
—end example ] - 末端的例子]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM