簡體   English   中英

c ++模板類; 具有任意容器類型的函數,如何定義它?

[英]c++ template class; function with arbitrary container type, how to define it?

好的,簡單的模板問題。 假設我將模板類定義為:

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函數...我需要它能夠使用某種標准容器,這就是為什么我包含模板/ typename C部分來定義容器類型。 但顯然這不是正確的方法,因為我的測試類然后抱怨:

錯誤:此范圍內未聲明“bar”

那么我將如何以正確的方式實現我的條形函數呢? 也就是說,作為我的模板類的函數,使用任意容器類型...我的模板類的其余部分工作正常(有其他函數不會導致錯誤),它只是一個有問題的函數。

編輯:好的,所以特定的函數(bar)是eraseInRange函數,它擦除指定范圍內的所有元素:

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

它將如何使用的一個例子是:

eraseInRange(v, 7, 19);

其中v是這種情況下的向量。

編輯2:傻我! 我本來應該在我班級之外宣布這個功能,而不是在它里面...這是非常令人沮喪的錯誤。 無論如何,感謝大家的幫助,雖然問題有點不同,但信息確實幫助我構建了這個功能,因為在找到原來的問題后,我確實得到了一些其他令人愉快的錯誤。 所以謝謝!


特質解決方案。

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

在某些情況下,解決方案可能不夠,因為它將匹配具有此類簽名的任何模板(例如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());
}

有關更多此類示例,請查看<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)。

在模板模板參數上模板化模板:

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成員類型的任何東西。 可以設想用於檢查成員函數和迭代器的更復雜的特征; 例如, 漂亮的打印機實現了其中的一些。

這是答案的最新和擴展版本,以及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(),以確保我們有迭代器行為。 有關更多說明,請參閱我的博文

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM