How to add element to various container depending of type using template

前端 未结 5 595
长发绾君心
长发绾君心 2021-01-22 08:04

I\'ve got rather silly question but I hope that you can help me with that.

I\'ve got class with multiple vectors, and this vectors have different storage types.

<
相关标签:
5条回答
  • 2021-01-22 08:44

    This may be a use case for a Tagged Tuple library. It makes it possible to index containers by associated type. So code dealing with dozens of similar vector<std::pair<std::string, B>> V1; fields becomes generic:

    #include <vtt/container/Tagged Tuple.hpp>
    
    #include <string>
    #include <type_traits>
    #include <vector>
    #include <utility>
    
    class BaseClass{::std::string Name;};
    class B : public BaseClass{};
    class C : public BaseClass{};
    
    class A
    {
        public: template<typename x_Item> using
        t_Vector = ::std::vector<::std::pair<::std::string, x_Item>>;
    
        public: using
        t_Vectors = ::n_vtt::n_container::t_TaggedTuple
        <// index type -> value type mapping
            B, t_Vector<B>
        ,   C, t_Vector<C>
        >;
    
        private: t_Vectors m_vectors;
    
        public: template<typename x_Item> void
        Add_Item(x_Item && item)
        {
            m_vectors
                // invoke either Get_MutableItem<B> or Get_MutableItem<C>
                .Get_MutableItem<::std::remove_reference_t<::std::remove_cv_t<x_Item>>>()
                // add item into corresponding std::vector
                .emplace_back(::std::string{}, ::std::forward<x_Item>(item));
        }
    };
    
    int main()
    {
        A a;
        a.Add_Item(B{});
        C c{};
        a.Add_Item(c);
        return 0;
    }
    
    0 讨论(0)
  • 2021-01-22 08:47

    You might use "generic getter" for your vector:

    class A
    {
    public:
    
        template <typename T>
        std::vector<std::pair<std::string, T>>& getVector() {
            auto vectors = std::tie(V1, V2);
            return std::get<std::vector<std::pair<std::string, T>>&>(vectors);
        }
    
        template <class T>
        void addElement(T Obj) {
            getVector<T>().emplace_back(Obj.Name, Obj);
        }
    
        std::vector<std::pair<std::string, B>> V1;
        std::vector<std::pair<std::string, C>> V2;
    };
    

    Changing your member might make sense to have std::tuple directly. and you might want to templatize the whole class:

    template <typename ... Ts>
    class A_Impl
    {
    private:
        template <typename T>
        decltype(auto) getVector() const {
            return std::get<std::vector<std::pair<std::string, T>>>(Vs);
        }
        template <typename T>
        decltype(auto) getVector() {
            return std::get<std::vector<std::pair<std::string, T>>>(Vs);
        }
    public:
    
        template <class T>
        void addElement(T Obj) {
            getVector<T>().emplace_back(Obj.Name, Obj);
        }
    private:
        std::tuple<std::vector<std::pair<std::string, Ts>>...> Vs;
    };
    
    using A = A_Impl<B, C>;
    
    0 讨论(0)
  • 2021-01-22 08:50

    If you are going to use your vectors in a few places you could specialise a template to get the correct vector once, then your main template code can be generic:

    class A{
    public:
    
      template < typename T >
      void addElement(T obj)
      {
        getVector<T>().push_back(std::make_pair(obj.Name,obj));
      }
    
      template < typename T >
      T& getElement(size_t index)
      {
        return getVector<T>().at(index).second;
      }
    
    private:
      vector<std::pair<std::string, B>> V1;
      vector<std::pair<std::string, C>> V2;
    
      template < typename T >
      vector<std::pair<std::string, T>>& getVector();
    };
    
    template <>
    vector<std::pair<std::string, B>>& A::getVector<B>() { return V1; }
    template <>
    vector<std::pair<std::string, C>>& A::getVector<C>() { return V2; }
    
    0 讨论(0)
  • 2021-01-22 08:51

    What if you make use of the shared BaseClass like this?? You will have to create a common interface for BaseClass, I am not sure how much B and C will differ in terms of functionality though.

    class BaseClass{
    public:
        std::string Name;
    };
    
    class B : public BaseClass{
    };
    class C : public BaseClass{
    };
    
    class A{
    public:
        std::vector<std::pair<std::string, std::unique_ptr<BaseClass>> > V1;
        std::vector<std::pair<std::string, std::unique_ptr<BaseClass>> > V2;
    
        template <class T>  void addElement(T Obj)
        {
    
            std::pair<std::string, std::unique_ptr<T>> AddedPair(Obj.Name, std::make_unique<T>(Obj));
    
            if (typeid(T) == typeid(B)) 
                V1.push_back(AddedPair);
    
            else if (typeid(T) == typeid(C)) 
                V2.push_back(AddedPair);
        }  
    };
    
    int main()
    {
      A a;
      B b;
      C c;
    
      a.addElement<B>(b) ;//-> then element b is added to vector V1
      a.addElement<C>(c) ;//-> then element c is added to vector V2
    }
    
    0 讨论(0)
  • 2021-01-22 08:56

    Instead of having

    template <class T>  void addElement(T Obj);
    

    Just overload the function instead. That would give you

    void addElement(const B& Obj)
    {
        V1.push_back({Obj.Name, Obj});
    }
    
    void addElement(const C& Obj)
    {
        V2.push_back({Obj.Name, Obj});
    }
    

    This saves you all the syntax of specializing the template or needing C++17 and if constexpr to make the decision at compile time.


    The reason

    template <class T>  void addElement(T Obj){
    std::pair<std::string, T> AddedPair(Obj.Name, Obj);
    
        if (typeid(T) == typeid(B)) 
            V1.push_back(AddedPair);
    
        if (typeid(T) == typeid(C)) 
            V2.push_back(AddedPair);
    
    }
    

    Doesn't work is the code in each if block needs to be valid (even if it could never be reached), but it can't be because you would be adding a different type into the vectors. if constexpr helps but I find overloading is just as much typing and makes the code non-backwards compatible.

    That means you would either have to specialize the template like

    template <class T>  void addElement(T Obj);
    
    template <>  void addElement(B Obj)
    {
        V1.push_back({Obj.Name, Obj});
    }   
    template <>  void addElement(C Obj)
    {
        V1.push_back({Obj.Name, Obj});
    }
    

    or using if constexpr:

    template <class T>  void addElement(T Obj){
    std::pair<std::string, T> AddedPair(Obj.Name, Obj);
    
        if constexpr(std::is_same_v<T, B>) 
            V1.push_back(AddedPair);
    
        if constexpr(std::is_same_v<T, C>) 
            V2.push_back(AddedPair);
    
    }       
    
    0 讨论(0)
提交回复
热议问题