web-dev-qa-db-fra.com

classe de modèle c ++; fonction avec un type de conteneur arbitraire, comment le définir?

Ok, question de modèle simple. Dites que je définis ma classe de modèle quelque chose comme ceci:

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

La question concerne ma fonction de barre ... J'en ai besoin pour pouvoir utiliser un conteneur standard, c'est pourquoi j'ai inclus la partie C template/typename, pour définir ce type de conteneur. Mais apparemment, ce n'est pas la bonne façon de le faire, puisque ma classe de test se plaint alors de ce qui suit:

erreur: 'bar' n'a pas été déclaré dans cette étendue

Alors, comment pourrais-je m'y prendre pour mettre en place mon bar correctement? En d’autres termes, en fonction de ma classe de modèle, avec un type de conteneur arbitraire ... le reste de ma classe de modèle fonctionne bien (a d’autres fonctions qui ne génèrent pas d’erreur), c’est simplement une fonction qui pose problème.

EDIT: OK, donc la fonction spécifique (barre) est une fonction eraseInRange, qui efface tous les éléments dans une plage spécifiée:

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

Et un exemple d'utilisation serait:

eraseInRange(v, 7, 19);

où v est un vecteur dans ce cas.

EDIT 2: Silly moi! Je devais déclarer la fonction en dehors de ma classe, pas dans celle-ci ... erreur assez frustrante à faire. Quoi qu'il en soit, merci à tous pour l'aide, bien que le problème soit un peu différent, les informations m'ont aidé à construire la fonction, car après avoir trouvé mon problème initial, j'ai obtenu d'autres erreurs agréables. Alors merci!

17
Fault


Solution de Traits.

Généraliser pas plus que nécessaire et pas moins.

Dans certains cas, cette solution peut ne pas être suffisante car elle correspond à tout modèle comportant une telle signature (par exemple, shared_ptr), auquel cas vous pouvez utiliser type_traits, très similaire à duck-typing (les modèles sont généralement tapés à la machine ).

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

Pasrestreint artificiellement le type de types pour lesquels ils sont valables (pourquoi le devraient-ils?). Imaginons, dans l'exemple ci-dessus, que vous ayez besoin d'accéder à un objet const_iterator. type_traits pour mettre ces contraintes sur votre fonction.


Ou simplement comme le fait la bibliothèque standard

Généraliser pas plus que nécessaire et pas moins.

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

Pour plus d'exemples, regardez dans <algorithm>.

La force de cette approche réside dans sa simplicité et repose sur des concepts tels que ForwardIterator. Cela fonctionnera même pour les tableaux. Si vous souhaitez signaler des erreurs directement dans la signature, vous pouvez les combiner avec des traits.


std conteneurs avec signature comme std::vector_ (non recommandé)

La solution la plus simple est déjà approchée par Kerrek SB, bien qu’elle soit invalide en C++. La variante corrigée va comme suit:

#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)
{
  //
}

Cependant : cela ne fonctionnera que pour les conteneurs qui ont exactement deux arguments de type template, donc échouera misérablement pour std::mapmerci Luc Danton).


Tout type d'argument de modèle secondaire (_ (non recommandé)

La version corrigée pour tout nombre de paramètres secondaires est la suivante:

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

Cependant : cela échouera quand même pour les conteneurs non-template (merci Luc Danton).

30
Sebastian Mach

Créez le modèle sur un paramètre de modèle:

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

Si vous n'avez pas C++ 11, vous ne pouvez pas utiliser de modèles variadiques, et vous devez fournir autant de paramètres de modèle que votre conteneur en prend. Par exemple, pour un conteneur de séquence, vous aurez peut-être besoin de deux:

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

Ou, si vous souhaitez uniquement autoriser les allocateurs qui sont eux-mêmes des instances de modèle:

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

Comme je l'ai suggéré dans les commentaires, je préférerais personnellement que le conteneur entier soit un type basé sur un modèle et utilise des traits pour vérifier sa validité. Quelque chose comme ça:

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

Ceci est plus flexible car le conteneur peut maintenant contenir n'importe quel élément exposant le type de membre value_type. Des traits plus sophistiqués pour vérifier les fonctions membres et les itérateurs peuvent être conçus; Par exemple, la jolie imprimante en implémente quelques-unes.

6
Kerrek SB

Voici la dernière version développée de cette réponse et une amélioration significative par rapport à la réponse de Sabastian.

L'idée est de définir tous les traits des conteneurs STL. Malheureusement, cela devient très difficile et heureusement, beaucoup de gens ont travaillé à l’optimisation de ce code. Ces traits sont réutilisables, il suffit donc de copier et coller le code ci-dessous dans le fichier type_utils.hpp (n'hésitez pas à changer ces noms):

//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

Vous pouvez maintenant utiliser ces caractéristiques pour vous assurer que notre code n'accepte que les types de conteneur. Par exemple, vous pouvez implémenter une fonction add qui ajoute un vecteur à un autre comme ceci:

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

Notez que j'utilise begin () et end () de std namespace simplement pour m'assurer que nous avons le comportement de l'itérateur. Pour plus d'explications, voir mon blog .

0
Shital Shah