c ++模板类;具有任意容器类型的函数,如何定义它?

时间:2011-10-11 15:24:36

标签: c++ templates containers

好的,简单的模板问题。假设我将模板类定义为:

template<typename T>
class foo {
public:
    foo(T const& first, T const& second) : first(first), second(second) {}

    template<typename C>
    void bar(C& container, T const& baz) {
        //...
    }
private:
    T first;
    T second;
}

问题是关于我的条形函数...我需要它能够使用某种标准容器,这就是为什么我包含模板/ typename C部分来定义该容器类型。但显然这不是正确的方法,因为我的测试类然后抱怨:

错误:未在此范围内声明“bar”

那么我将如何以正确的方式实现我的条形函数呢?也就是说,作为我的模板类的函数,使用任意容器类型...我的模板类的其余部分工作正常(有其他函数不会导致错误),它只是一个有问题的函数。 / p>

编辑: 好的,所以特定的函数(bar)是eraseInRange函数,它擦除指定范围内的所有元素:

void eraseInRange(C& container, T const& firstElement, T const& secondElement) {...}

如何使用它的一个例子是:

eraseInRange(v, 7, 19);

其中v是这种情况下的向量。

编辑2: 傻我!我本来应该在我班级之外宣布这个功能,而不是在它里面...这是非常令人沮丧的错误。无论如何,感谢大家的帮助,虽然问题有点不同,但是信息确实帮助我构建了这个功能,因为在找到原来的问题后,我确实得到了一些其他令人愉快的错误。谢谢你!

4 个答案:

答案 0 :(得分:30)


特征解决方案。

概括不超过需要,而不是更少。

在某些情况下,解决方案可能不够,因为它会匹配任何带有此类签名的模板(例如shared_ptr),在这种情况下,您可以使用type_traits,非常类似于{{3 (模板通常是鸭子类型)。

#include <type_traits>

// Helper to determine whether there's a const_iterator for T.
template<typename T>
struct has_const_iterator
{
private:
    template<typename C> static char test(typename C::const_iterator*);
    template<typename C> static int  test(...);
public:
    enum { value = sizeof(test<T>(0)) == sizeof(char) };
};


// bar() is defined for Containers that define const_iterator as well
// as value_type.
template <typename Container>
typename std::enable_if<has_const_iterator<Container>::value,
                        void>::type
bar(const Container &c, typename Container::value_type const & t)
{
  // Note: no extra check needed for value_type, the check comes for
  //       free in the function signature already.
}


template <typename T>
class DoesNotHaveConstIterator {};

#include <vector>
int main () {
    std::vector<float> c;
    bar (c, 1.2f);

    DoesNotHaveConstIterator<float> b;
    bar (b, 1.2f); // correctly fails to compile
}

一个好的模板通常人为地限制它们有效的类型(为什么它们?)。但是想象一下,在上面的示例中,您需要访问对象const_iterator,然后您可以使用SFINAE和type_traits将这些约束放在您的函数上。


或者只是像标准库一样

概括不超过需要,而不是更少。

template <typename Iter>
void bar (Iter it, Iter end) {
    for (; it!=end; ++it) { /*...*/ }
}

#include <vector>
int main () {
    std::vector<float> c;
    bar (c.begin(), c.end());
}

有关更多此类示例,请查看<algorithm>

这种方法的优势在于它的简单性,并且基于 ForwardIterator 等概念。它甚至适用于数组。如果要在签名中报告错误,可以将其与特征结合起来。


带有std签名的

std::vector个容器(不推荐

最简单的解决方案已经由Kerrek SB近似,尽管它是无效的C ++。修正后的变体如下:

#include <memory> // for std::allocator
template <template <typename, typename> class Container, 
          typename Value,
          typename Allocator=std::allocator<Value> >
void bar(const Container<Value, Allocator> & c, const Value & t)
{
  //
}

然而:这只适用于具有两个模板类型参数的容器,因此std::map会失败(感谢Luc Danton)。


任何类型的辅助模板参数(不推荐

任何次要参数计数的更正版本如下:

#include <memory> // for std::allocator<>

template <template <typename, typename...> class Container, 
          typename Value,
          typename... AddParams >
void bar(const Container<Value, AddParams...> & c, const Value & t)
{
  //
}

template <typename T>
class OneParameterVector {};

#include <vector>
int main () {
    OneParameterVector<float> b;
    bar (b, 1.2f);
    std::vector<float> c;
    bar (c, 1.2f);
}

然而:对于非模板容器,这仍然会失败(感谢Luc Danton)。

答案 1 :(得分:6)

在模板模板参数上模板化模板:

template <template <typename, typename...> class Container>
void bar(const Container<T> & c, const T & t)
{
  //
}

如果您没有C ++ 11,则无法使用可变参数模板,并且必须提供与容器一样多的模板参数。例如,对于序列容器,您可能需要两个:

template <template <typename, typename> class Container, typename Alloc>
void bar(const Container<T, Alloc> & c, const T & t);

或者,如果您只想允许本身就是模板实例的分配器:

template <template <typename, typename> class Container, template <typename> class Alloc>
void bar(const Container<T, Alloc<T> > & c, const T & t);

正如我在评论中所建议的那样,我个人更喜欢将整个容器设为模板类型并使用特征来检查它是否有效。像这样:

template <typename Container>
typename std::enable_if<std::is_same<typename Container::value_type, T>::value, void>::type
bar(const Container & c, const T & t);

这更灵活,因为容器现在可以是暴露value_type成员类型的任何东西。可以设想用于检查成员函数和迭代器的更复杂的特征;例如,pretty printer实现了其中的一些。

答案 2 :(得分:1)

具有概念和范围的 C++20 解决方案

C++20中,通过添加概念范围库,我们可以用std::ranges::common_range简单地解决这个问题:

void printContainer(const std::ranges::common_range auto & container);
{
    for(const auto& item : container) std::cout << item;
}

这里,common_range是所有stl容器都满足的概念。您可以通过以下方式获取 container 的值类型:

std::ranges::range_value_t<decltype(container)>

您还可以使用定义良好的迭代器类型以及 it begin()it end() 函数创建自己的容器类型,以满足该概念。

  • 或者,您也可以使用 std::ranges::range,它的要求比 common_range 稍微宽松一些,因此可以允许更多自定义类型。

尝试使用不令人满意的类型调用函数会导致错误,例如 template argument deduction/substitution failed: constraints not satisfied

答案 3 :(得分:0)

这是this answer的最新版和扩展版,与Sabastian的回答有显着改善。

这个想法是定义STL容器的所有特征。不幸的是,这变得非常棘手,幸运的是很多人都在调整这段代码。这些特性是可重用的,所以只需复制并过去名为type_utils.hpp的文件中的代码(随意更改这些名称):

//put this in type_utils.hpp 
#ifndef commn_utils_type_utils_hpp
#define commn_utils_type_utils_hpp

#include <type_traits>
#include <valarray>

namespace common_utils { namespace type_utils {
    //from: https://raw.githubusercontent.com/louisdx/cxx-prettyprint/master/prettyprint.hpp
    //also see https://gist.github.com/louisdx/1076849
    namespace detail
    {
        // SFINAE type trait to detect whether T::const_iterator exists.

        struct sfinae_base
        {
            using yes = char;
            using no  = yes[2];
        };

        template <typename T>
        struct has_const_iterator : private sfinae_base
        {
        private:
            template <typename C> static yes & test(typename C::const_iterator*);
            template <typename C> static no  & test(...);
        public:
            static const bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
            using type =  T;

            void dummy(); //for GCC to supress -Wctor-dtor-privacy
        };

        template <typename T>
        struct has_begin_end : private sfinae_base
        {
        private:
            template <typename C>
            static yes & f(typename std::enable_if<
                std::is_same<decltype(static_cast<typename C::const_iterator(C::*)() const>(&C::begin)),
                             typename C::const_iterator(C::*)() const>::value>::type *);

            template <typename C> static no & f(...);

            template <typename C>
            static yes & g(typename std::enable_if<
                std::is_same<decltype(static_cast<typename C::const_iterator(C::*)() const>(&C::end)),
                             typename C::const_iterator(C::*)() const>::value, void>::type*);

            template <typename C> static no & g(...);

        public:
            static bool const beg_value = sizeof(f<T>(nullptr)) == sizeof(yes);
            static bool const end_value = sizeof(g<T>(nullptr)) == sizeof(yes);

            void dummy(); //for GCC to supress -Wctor-dtor-privacy
        };

    }  // namespace detail

    // Basic is_container template; specialize to derive from std::true_type for all desired container types

    template <typename T>
    struct is_container : public std::integral_constant<bool,
                                                        detail::has_const_iterator<T>::value &&
                                                        detail::has_begin_end<T>::beg_value  &&
                                                        detail::has_begin_end<T>::end_value> { };

    template <typename T, std::size_t N>
    struct is_container<T[N]> : std::true_type { };

    template <std::size_t N>
    struct is_container<char[N]> : std::false_type { };

    template <typename T>
    struct is_container<std::valarray<T>> : std::true_type { };

    template <typename T1, typename T2>
    struct is_container<std::pair<T1, T2>> : std::true_type { };

    template <typename ...Args>
    struct is_container<std::tuple<Args...>> : std::true_type { };

}}  //namespace
#endif

现在您可以使用这些特性来确保我们的代码只接受容器类型。例如,您可以实现将一个向量附加到另一个向量的追加函数,如下所示:

#include "type_utils.hpp"

template<typename Container>
static typename std::enable_if<type_utils::is_container<Container>::value, void>::type
append(Container& to, const Container& from)
{
    using std::begin;
    using std::end;
    to.insert(end(to), begin(from), end(from));
}

请注意,我在std命名空间中使用begin()和end()只是为了确保我们有迭代器行为。有关详细说明,请参阅my blog post