Generating one class member per variadic template argument

后端 未结 5 1671
后悔当初
后悔当初 2020-12-02 14:52

I have a template class where each template argument stands for one type of value the internal computation can handle. Templates (instead of function overloading) are needed

相关标签:
5条回答
  • 2020-12-02 14:56

    There is a proposal to allow this kind of expansion, with the intuitive syntax: P1858R1 Generalized pack declaration and usage. You can also initialize the members and access them by index. You can even support structured bindings by writing using... tuple_element = /*...*/:

    template <typename... Ts>
    class MyClass {
        std::vector<Ts>... elems;
    public:
        using... tuple_element = std::vector<Ts>;
    
        MyClass() = default;
        explicit MyClass(std::vector<Ts>... args) noexcept
            : elems(std::move(args))...
        {
        }
    
        template <std::size_t I>
            requires I < sizeof...(Ts)
        auto& get() noexcept
        {
            return elems...[I];
        }
    
        template <std::size_t I>
            requires I < sizeof...(Ts)
        const auto& get() const
        {
            return elems...[I];
        }
    
        // ...
    };
    

    Then the class can be used like this:

    using Vecs = MyClass<int, double>;
    
    Vecs vecs{};
    vecs.[0].resize(3, 42);
    
    std::array<double, 4> arr{1.0, 2.0, 4.0, 8.0};
    vecs.[1] = {arr.[:]};
    
    // print the elements
    // note the use of vecs.[:] and Vecs::[:]
    (std::copy(vecs.[:].begin(), vecs.[:].end(),
               std::ostream_iterator<Vecs::[:]>{std::cout, ' '},
     std::cout << '\n'), ...);
    
    0 讨论(0)
  • 2020-12-02 15:03

    One way to do such a thing, as mentioned in πάντα-ῥεῖ's comment is to use a tuple. What he didn't explain (probably to save you from yourself) is how that might look.

    Here is an example:

    using namespace std;
    
    // define the abomination    
    template<typename...Types>
    struct thing
    {
        thing(std::vector<Types>... args)
        : _x { std::move(args)... }
        {}
    
        void print()
        {
            do_print_vectors(std::index_sequence_for<Types...>());
        }
    
    private:
        template<std::size_t... Is>
        void do_print_vectors(std::index_sequence<Is...>)
        {
            using swallow = int[];
            (void)swallow{0, (print_one(std::get<Is>(_x)), 0)...};
        }
    
        template<class Vector>
        void print_one(const Vector& v)
        {
            copy(begin(v), end(v), ostream_iterator<typename Vector::value_type>(cout, ","));
            cout << endl;
        }
    
    private:
        tuple<std::vector<Types>...> _x;
    };
    
    
    // test it
    BOOST_AUTO_TEST_CASE(play_tuples)
    {
        thing<int, double, string> t {
            { 1, 2, 3, },
            { 1.1, 2.2, 3.3 },
            { "one"s, "two"s, "three"s }
        };
    
        t.print();
    }
    

    expected output:

    1,2,3,
    1.1,2.2,3.3,
    one,two,three,
    
    0 讨论(0)
  • 2020-12-02 15:10

    An alternate solution that doesn't use tuples is to use CRTP to create a class hierarchy where each base class is a specialization for one of the types:

    #include <iostream>
    #include <string>
    
    template<class L, class... R> class My_class;
    
    template<class L>
    class My_class<L>
    {
    public:
    
    protected:
      L get()
      {
        return val;
      }
    
      void set(const L new_val)
      {
        val = new_val;
      }
    
    private:
      L val;
    };
    
    template<class L, class... R>
    class My_class : public My_class<L>, public My_class<R...>
    {
    public:
      template<class T>
      T Get()
      {
        return this->My_class<T>::get();
      }
    
      template<class T>
      void Set(const T new_val)
      {
        this->My_class<T>::set(new_val);
      }
    };
    
    int main(int, char**)
    {
      My_class<int, double, std::string> c;
      c.Set<int>(4);
      c.Set<double>(12.5);
      c.Set<std::string>("Hello World");
    
      std::cout << "int: " << c.Get<int>() << "\n";
      std::cout << "double: " << c.Get<double>() << "\n";
      std::cout << "string: " << c.Get<std::string>() << std::endl;
    
      return 0;
    }
    
    0 讨论(0)
  • 2020-12-02 15:15

    As you have already been hinted, the best way is to use a tuple:

    template<typename ...AcceptedTypes> // e.g. MyClass<T1, T2>
    class MyClass {
        std::tuple<std::vector<AcceptedTypes>...> vectors;
    };
    

    This is the only way to multiply the "fields" because you cannot magically make it spell up the field names. Another important thing may be to get some named access to them. I guess that what you're trying to achieve is to have multiple vectors with unique types, so you can have the following facility to "search" for the correct vector by its value type:

    template <class T1, class T2>
    struct SameType
    {
        static const bool value = false;
    };
    
    template<class T>
    struct SameType<T, T>
    {
        static const bool value = true;
    };
    
    template <typename... Types>
    class MyClass
    {
         public:
         typedef std::tuple<vector<Types>...> vtype;
         vtype vectors;
    
         template<int N, typename T>
         struct VectorOfType: SameType<T,
            typename std::tuple_element<N, vtype>::type::value_type>
         { };
    
         template <int N, class T, class Tuple,
                  bool Match = false> // this =false is only for clarity
         struct MatchingField
         {
             static vector<T>& get(Tuple& tp)
             {
                 // The "non-matching" version
                 return MatchingField<N+1, T, Tuple,
                        VectorOfType<N+1, T>::value>::get(tp);
             }
         };
    
         template <int N, class T, class Tuple>
         struct MatchingField<N, T, Tuple, true>
         {
            static vector<T>& get(Tuple& tp)
            {
                return std::get<N>(tp);
            }
         };
    
         template <typename T>
         vector<T>& access()
         {
             return MatchingField<0, T, vtype,
                    VectorOfType<0, T>::value>::get(vectors);
         }
    };
    

    Here is the testcase so you can try it out:

    int main( int argc, char** argv )
    {
        int twelf = 12.5;
        typedef reference_wrapper<int> rint;
    
        MyClass<float, rint> mc;
        vector<rint>& i = mc.access<rint>();
    
        i.push_back(twelf);
    
        mc.access<float>().push_back(10.5);
    
        cout << "Test:\n";
        cout << "floats: " << mc.access<float>()[0] << endl;
        cout << "ints: " << mc.access<rint>()[0] << endl;
        //mc.access<double>();
    
        return 0;
    }
    

    If you use any type that is not in the list of types you passed to specialize MyClass (see this commented-out access for double), you'll get a compile error, not too readable, but gcc at least points the correct place that has caused the problem and at least such an error message suggests the correct cause of the problem - here, for example, if you tried to do mc.access<double>():

     error: ‘value’ is not a member of ‘MyClass<float, int>::VectorOfType<2, double>’
    
    0 讨论(0)
  • 2020-12-02 15:19

    Here is a less than perfectly efficient implementation using boost::variant:

    template<typename ... Ts>
    using variant_vector = boost::variant< std::vector<Ts>... >;
    
    template<typename ...Ts>
    struct MyClass {
      using var_vec = variant_vector<Ts...>;
      std::array<var_vec, sizeof...(Ts)> vecs;
    };
    

    we create a variant-vector that can hold one of a list of types in it. You have to use boost::variant to get at the contents (which means knowing the type of the contents, or writing a visitor).

    We then store an array of these variant vectors, one per type.

    Now, if your class only ever holds one type of data, you can do away with the array, and just have one member of type var_vec.

    I cannot see why you'd want one vector of each type. I could see wanting a vector where each element is one of any type. That would be a vector<variant<Ts...>>, as opposed to the above variant<vector<Ts>...>.

    variant<Ts...> is the boost union-with-type. any is the boost smart-void*. optional is the boost there-or-not.

    template<class...Ts>
    boost::optional<boost::variant<Ts...>> to_variant( boost::any );
    

    may be a useful function, that takes an any and tries to convert it to any of the Ts... types in the variant, and returns it if it succeeds (and returns an empty optional if not).

    0 讨论(0)
提交回复
热议问题