【问题标题】:c++ template class; function with arbitrary container type, how to define it?c++模板类;具有任意容器类型的函数,如何定义它?
【发布时间】:2011-10-11 15:24:36
【问题描述】:

好的,简单的模板问题。假设我定义我的模板类是这样的:

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

问题是关于我的 bar 函数...我需要它能够使用某种标准容器,这就是我包含模板/类型名称 C 部分的原因,以定义该容器类型。但显然这不是正确的做法,因为我的测试班然后抱怨:

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

那么我将如何以正确的方式实现我的 bar 功能?也就是说,作为我的模板类的函数,具有任意容器类型......我的模板类的其余部分工作正常(具有不会导致错误的其他函数),只是那个函数有问题。

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

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

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

eraseInRange(v, 7, 19);

这里的 v 是一个向量。

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

【问题讨论】:

  • 请提供一个示例,说明您如何使用 foo 类和 foot::bar 方法。问题可能出在您使用的代码中。
  • 您发布的代码编译良好。您实际上在哪里遇到问题?
  • 你的测试类没有发生 inherit from foo?见parashift.com/c++-faq-lite/templates.html#faq-35.19
  • 我希望我的编辑能澄清一点?为练习提供了测试类,因此我不应该更改调用该方法的方式,但它抱怨没有声明 eraseInRange 方法...
  • 由于我们不知道您的测试驱动程序在做什么,因此我们帮不上什么忙。该课程是否适合您,例如如果您自己使用eraseInRange 和矢量?

标签: c++ templates containers


【解决方案1】:


性状解。

泛化不超过需要,也不能少。

在某些情况下,该解决方案可能还不够,因为它会匹配具有此类签名的任何模板(例如shared_ptr),在这种情况下,您可以使用type_traits,非常类似于duck-typing(模板是鸭子一般输入)。

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

有关更多此类示例,请查看&lt;algorithm&gt;

这种方法的优势在于其简单性,并且基于 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)。

【讨论】:

  • 这与error: 'bar' was not declared in this scope 有什么关系?抱歉,但 OP 的代码似乎是正确的。我在这里看到了很多模板魔法,但这些都不是人们通常在函数模板中接受容器的方式。
  • 泛型编程发生了什么? template&lt;typename Container&gt; void foo(Container&amp; c) { /* use typename Container::value_type as needed etc. */ ...
  • 请注意:在编译时确保 Container::value_type 和 T 相同(或 Container 是一个容器)是静态断言的工作。比较您从无辜错误中得到的错误诊断:ideone.com/gVVFU(这有多大帮助!)和ideone.com/ILIGP(在哪里和为什么)。仅当您要在这些类型不相同或第一个参数不是容器的情况下提供重载时,才建议您在上面看到。
  • @Luc:什么都没发生,这仍然是最好的解决方案(请参阅我的回答中关于特征检查的部分)。我,我被发布一个使用“模板”这个词四次的真实句子的前景所吸引......
  • @UncleBens: 0) 问题是So how would I go about implementing my bar function the proper way?。 1)定义“正常”。标准库通常适用于迭代器对(我的第四个建议)。例如。 std::count的签名:count ( ForwardIterator first, ForwardIterator last, const T&amp; value );
【解决方案2】:

在模板模板参数上制作模板模板:

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

正如我在 cmets 中所建议的,我个人更愿意将整个容器设为模板化的 type 并使用特征来检查它是否有效。像这样的:

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 实现了其中的一些。

【讨论】:

  • that 应该起作用的任何理由。 (总的来说,这应该是非常不寻常的事情......)
  • @UncleBens:什么意思?您可以使用模板匹配各种事物。我并不是说这是最好的主意(就我个人而言,我可能更喜欢typename C::value_type 的特征检查),但是嘿,这是一个选项。
  • 但是你在哪里展开包呢?
  • 这如何解释为什么原始代码会失败(当然,问题没有提供足够的信息)以及为什么可以预期会成功?从问题中甚至不清楚 T 应该是C::value_type。 - 如果没有这个解释,这对我来说听起来像是 woodoo 编程。
  • @CaptainGiraffe:特征更可取,因为它们允许更通用的容器。使用模板模板参数,您必须预期正确的模板签名。通过特征检查,您可以拥有完全任意的容器,只要它们公开 value_type
【解决方案3】:

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

C++20中,通过添加ConceptsRanges库,我们可以用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 之类的错误。

【讨论】:

  • std::ranges::common_range 之后的 auto 是做什么的?
  • @user3520616 autofoo(const auto param); 中的 auto 相同。它将被推断为 param 的任何类型。但是,将common_range 放在auto 前面意味着只能将遵循common_range 概念的类型放在其中。
【解决方案4】:

这是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

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

#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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-04
    • 1970-01-01
    • 2013-08-12
    相关资源
    最近更新 更多