Generating one class member per variadic template argument

19,767

Solution 1

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

Solution 2

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

Solution 3

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,

Solution 4

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'), ...);
Share:
19,767
user1101674
Author by

user1101674

Updated on June 02, 2022

Comments

  • user1101674
    user1101674 about 2 years

    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 because the values are passed as boost::any and their types are not clear before runtime.

    To properly cast to the correct types, I would like to have a member list for each variadic argument type, something like this:

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

    Or alternatively, I'd like to store the template argument types in a list, as to do some RTTI magic with it (?). But how to save them in a std::initializer_list member is also unclear to me.

    Thanks for any help!

  • TemplateRex
    TemplateRex over 9 years
    ah, I missed the extra constructor taking vector arguments. I was messing around with initializer lists of the std::tuple<std::vector>> and ran into the explicit nature of these constructors.
  • Richard Hodges
    Richard Hodges over 9 years
    It's a fun exercise but if I saw this in our production code I'd be looking to fire someone :-)
  • TemplateRex
    TemplateRex over 9 years
    this type of exercise can have benefits, e.g. in data oriented design you are often looking at std::tuple<std::vector> instead of std::vector<std::tuple> because of better caching etc. It can be convenient however to provide the interface of the latter on top of the data layout of the former. If you want to avoid hand-coding such a transformation, some variadic tuple magic can come in handy!
  • πάντα ῥεῖ
    πάντα ῥεῖ over 9 years
    @RichardHodges Good to know you aren't my boss ;)! I've got something like this in production. Unfortunately it's one of my most downvoted questions (where I think the DV's were just given as conciously chosen revenge).
  • user1101674
    user1101674 over 9 years
    Thanks! The code depends on c++14 features not available to my VS2013 though. Is something similar feasible with c++11 only?
  • Yakk - Adam Nevraumont
    Yakk - Adam Nevraumont over 9 years
    @user1101674 writing your own index_sequence_for is easy, and should be easy to find using google.
  • user1101674
    user1101674 over 9 years
    Works good so far, thanks! What, however, if I wanted a vector<std::reference_wrapper<T>> instead? I get the following errors: error C2504: 'std::tuple_element<0,std::tuple<>>' : base class undefined see reference to class template instantiation 'std::tuple_element<1,std::tuple<std::vector<std::reference_‌​wrapper<std::referen‌​ce_wrapper<unsigned int>>,std::allocator<std::reference_wrapper<std::reference_w‌​rapper<unsigned int>>>>>>' being compiled ...
  • Ethouris
    Ethouris over 9 years
    Did you #include <functional> and compile in C++11 mode? I changed my testcase a bit, used reference_wrapper<int>, pushed an int variable and everything still normally works. I'll put the whole testcase function so that you can see it.
  • user1101674
    user1101674 over 9 years
    Thanks a lot, it works! What triggered the compiler errors was that I had some code, as described above by Richard Hodges, which tried to automatically insert a vector of boost::any's into the member vectors of the corresponding types.
  • Ethouris
    Ethouris over 9 years
    It must work, I spent quite a big effort to define it - but it was worth it, even as an excercise for myself :)
  • Benji XVI
    Benji XVI about 8 years
    This is a fantastic piece of template wizardry, and I've learnt some interesting things from it. Thank you! I have one concern – it looks like it creates a set of templated functions, one for each template type. The call stack of the generated function will be N levels deep for the Nth type, as MatchingField::get() calls itself recursively. My question is: Is the compiler likely to be able to squash this down into one function, or even to a single inlined pointer dereference? Since everything is static at runtime, it seems it should be possible, but I'm not 100% sure.
  • Ethouris
    Ethouris almost 8 years
    You can even go further and provide in this MyClass some method forwarders to these vectors, which will select correct vector basing on type of the value you passed. For example, you can add a push_back method to this class, which will extract the correct vector by the type of data passed to MyClass::push_back and then forward it o this vector's push_back method.
  • Caleth
    Caleth almost 6 years
    You don't need the MatchingField machinery, you can just use the type overload of std::get, looking for vector<T>
  • Ethouris
    Ethouris almost 6 years
    This MatchingField machinery's purpose is exactly this "looking for". So I don't understand how std::get could be of any help here.
  • Eyal
    Eyal almost 3 years
    I think that this requires that each element have a unique type. Is there a way around that?
  • Eyal
    Eyal almost 3 years
    An advantage to this solution as compared to the tuple solution is that this one will use less memory when the data types aren't aligned. For example, a tuple of uint32 and uint16 is 8 bytes in size, not 6.
  • Arda
    Arda over 2 years
    @Eyal, this solution should also result in a size of 8 bytes for the example you give. The uint32 will cause the minimum alignment of the class to be 4 bytes, so the added 2 bytes of uint16 from second parent class will need to be followed by 2 padding bytes in order to align the class correctly. Even if you swap the parent classes, you should still end up with 8 bytes, as the uint16 will still need 2 padding bytes after it in order for the uint32 that comes after it to be aligned correctly. So either way, you should end up with 8 bytes unless you use some non-standard packing pragma.
  • Arda
    Arda over 2 years
    Overall, the tuple-based solution seems to be the more efficient one, size-wise. For example, if you have a uint32, uint16, uint8, and uint64, in that order, the tuple-based solution results in a class size of 16 bytes, while the inheritance-based (this) solution results in a class size of 32 bytes using the latest versions of both clang and gcc (haven't tested with msvc). You can see it in action here: godbolt.org/z/vfd6zqzdz . If you order the types from large to small, you get the same efficiency in both cases, but that can be difficult with more complex types.
  • Eugene W.
    Eugene W. about 2 years
    Dear @L. F. Could you explain more you solution? I have tried this but the program fails with a lot of errors: see here: godbolt.org/z/6h6q8h574
  • L. F.
    L. F. about 2 years
    @EugeneW. This was a proposal that hasn’t been accepted to the best of my knowledge, so I don’t think it works in any current compiler. Maybe check back in a few years :)