我需要创建一个尽可能通用的函数,假设我有几个map
可以使用 -
int main()
{
map<int, string> m1 = {{0, "abc"}, {1, "def"}, {2, "ghi"}} ;
map<int, double> m2 = {{0, 0.5}, {1, 0.6}, {2, 0.7}} ;
map<int, vector<string>> m3 = {{0, {"abc", "def"}},
{1, {"ghi", "ijk"}}};
dosomething(m1);
dosomething(m2);
}
我的template
d dosomething()
函数看起来像这样 -
template <typename A, typename B>
void dosomething(map<A,B> m)
{
for(auto e : m)
{
// do something with e
}
}
现在,如果我想编写一个适用于任何类型的map
(即map<int, vector<string>
或map<int, string>
或map<int, double>
)的函数,我该怎么做?如果我能做到这样的话会非常好 -
template <typename A, typename B>
void dosomething(map<A,B> m)
{
for(auto me: m)
{
if (the type of B is not a standard container type)
{
// do something with me.second
}
else if(the type of B is a something from the standard container)
{
for(int i = 0 ; i < (me.second).size() ; i++)
// do something with me.second[i]
}
}
}
我如何用c ++做到这一点?我假设编译器遵循c ++ 11规范。
答案 0 :(得分:3)
模板实例化中的代码用于编译。因此,根据模板参数的类型执行不同的操作时,通常无法在一个函数中使用它们。相反,您将处理委托给适当重载的函数,该函数本身可以是有条件地适用的模板。例如,您的dosomething()
函数可能如下所示:
// suitable declaration/definition of dosomething_apply() go here; see below
template <typename A, typename B>
void dosomething(std::map<A, B> const& m) {
for (auto&& me: m) {
dosomething_apply(me.second);
}
}
顺便说一句:不使用for (auto e: m)
,除非您有充分的理由说明为什么需要此表单!在大多数情况下,这是一个性能问题。同样,你不应该通过值传递更大的对象,而是使用合适的引用类型传递它们。
稍微棘手的业务是确定dosomething_apply()
的参数是否是标准容器:当然没有类型特征将所有标准分类。但是,可以创建相应的类型特征。接下来的问题是,这实际上是否是您想要检测的内容,因为您在元素上使用索引的假设布局意味着me.second
具有基于索引的下标运算符,该运算符仅对std::vector<...>
,{{1 },std::dequeue<...>
,std::basic_string<...>
和std::array<...>
(不确定这是否是提供下标的完整容器集)。还有其他容器没有下标操作,例如std::bitset<...>
,std::list<...>
,std::map<...>
等
但是,所有这些容器(std::unordered_map<...>
除外)都提供了一个迭代器接口。检测类型是否提供迭代器接口并在std::bitset<...>
的实现中使用此接口可能更合理。由于您似乎设置为使用基于索引的下标,因此下面的示例处理上面提到的一组类模板。
第一步是创建一个合适的traits类,它检测要求特殊处理的所需类型。在许多情况下,可以检测到合适的成员函数或成员类型的存在。由于您特别要求标准类型,因此有必要列出支持的类型,因为也可以检测非标准类的成员函数或成员类型。以下是名为dosomething_apply()
的示例traits类:
is_subscripted
它只是创建了一个默认的trait版本,它表明特征不是通过派生template <typename T>
struct is_subscripted: std::false_type {};
template <typename T, std::size_t N>
struct is_subscripted<std::array<T, N>>: std::true_type {};
template <std::size_t N>
struct is_subscripted<std::bitset<N>>: std::true_type {};
template <typename cT, typename Al>
struct is_subscripted<std::deque<cT, Al>>: std::true_type {};
template <typename cT, typename Tr, typename Al>
struct is_subscripted<std::basic_string<cT, Tr, Al>>: std::true_type {};
template <typename cT, typename Al>
struct is_subscripted<std::vector<cT, Al>>: std::true_type {};
来匹配的。然后,相应的模板专门用于上面列出的类模板,每个模板都来自std::false_type
。这样,std::true_type
形式的表达式可用于检测特征是否适用于is_specialized<T>::value
类型。
下一步是提供合适的处理函数。由于特征存在或缺席,使用T
是一种简单的方法:
enable_if_t<...>
使用template <typename T>
std::enable_if_t<!is_subscripted<T>::value>
dosomething_apply(T const& value) {
std::cout << "value=" << value << '\n';
}
template <typename T>
std::enable_if_t<is_subscripted<T>::value>
dosomething_apply(T const& range) {
for (auto size(range.size()), i(size - size); i != size; ++i) {
std::cout << "range=" << range[i] << '\n';
}
}
评估std::enable_if_t<Value>
Value
会产生true
(使用第二个参数,例如,void
来获得不同的类型)和功能模板变得适用。将std::enable_if_t<Value, double>
评估为Value
并使用它不会产生类型,并且会忽略函数模板(有关正在进行的解释,请参阅SFINAE)。
......那真的是这样。只需要将一个合适的程序组合在一起就可以了。下面是一个完整的程序,可以提供给C ++ 14编译器。在上面的代码中有一些C ++ 14的次要用途,例如使用false
代替std::enable_if_v<T>
。如果需要通过以前的C ++标准的编译器传递代码,那么用C ++ 11用法替换C ++ 14用法应该相对简单。
typename std::enable_if<T>::type