web-dev-qa-db-ja.com

c ++テンプレートクラス。任意のコンテナタイプの関数、それをどのように定義しますか?

わかりました、簡単なテンプレートの質問。テンプレートクラスを次のように定義するとします。

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関数を適切な方法で実装するにはどうすればよいですか?つまり、テンプレートクラスの関数として、任意のコンテナータイプを使用します...テンプレートクラスの残りの部分は正常に動作します(エラーにならない他の関数があります)。問題があるのはその1つの関数だけです。

編集:わかりましたので、特定の関数(バー)は、指定した範囲内のすべての要素を消去するeraseInRange関数です。

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

そしてそれがどのように使用されるかの例は次のようになります:

eraseInRange(v, 7, 19);

ここで、vはこの場合のベクトルです。

編集2:愚かな私!クラスではなくクラスの外で関数を宣言することになっていた...かなりイライラする間違いを犯している。とにかく、助けてくれてみんなに感謝します。問題は少し異なりましたが、情報は私が関数を構築するのに役立ちました。元の問題を見つけた後、私はいくつかの楽しいエラーを得たからです。ありがとうございます!

23
Fault

テンプレートテンプレートパラメーターでテンプレートを作成します。

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

C++ 11がない場合は、可変テンプレートを使用できません。また、コンテナーが取るのと同じ数のテンプレートパラメーターを指定する必要があります。たとえば、シーケンスコンテナーの場合、次の2つが必要になることがあります。

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

私がコメントで提案したように、個人的にはコンテナ全体をテンプレート化された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 はそれらのいくつかを実装します。

6
Kerrek SB

この答え の最新で拡張されたバージョンと、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

これで、これらの特性を使用して、コードがコンテナータイプのみを受け入れるようにすることができます。たとえば、次のように1つのベクトルを別のベクトルに追加する追加関数を実装できます。

#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()を使用していることに注意してください。詳細については 私のブログ投稿 を参照してください。

0
Shital Shah