c ++使用模板来创建最通用的函数

时间:2014-10-30 02:10:02

标签: c++ templates c++11

我需要创建一个尽可能通用的函数,假设我有几个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规范。

1 个答案:

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