44

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!

user1101674
  • 1,341
  • 2
  • 12
  • 15
  • 2
    You can forward the argument types to a [`std::tuple`](http://en.cppreference.com/w/cpp/utility/tuple). – πάντα ῥεῖ Jan 14 '15 at 11:21
  • Does it have to be separate members, or is okay to have a collection of vectors? Like e.g. an `std::array` of the vectors? [I don't know how to solve it either way, but your answer to this question might help those that do know how to solve this.] – Some programmer dude Jan 14 '15 at 11:28
  • A collection is fine, too. Though I am not sure whether that would help. At some point the container (std::vector) just needs to know the type. – user1101674 Jan 14 '15 at 11:36
  • If only one of the vectors will be populated at any one time, why not use a `boost::variant` of `vector`s? Or will more than one be populated at a time? – Yakk - Adam Nevraumont Jan 14 '15 at 14:39
  • See also: https://stackoverflow.com/a/13462578/1599699 – Andrew May 14 '18 at 20:12

5 Answers5

34

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>’
Evan Teran
  • 87,561
  • 32
  • 179
  • 238
Ethouris
  • 1,791
  • 13
  • 18
  • Works good so far, thanks! What, however, if I wanted a vector> 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::allocator>>>>>' being compiled ... – user1101674 Jan 15 '15 at 13:42
  • Did you `#include ` and compile in C++11 mode? I changed my testcase a bit, used `reference_wrapper`, pushed an `int` variable and everything still normally works. I'll put the whole testcase function so that you can see it. – Ethouris Jan 20 '15 at 18:44
  • 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. – user1101674 Jan 22 '15 at 11:08
  • It must work, I spent quite a big effort to define it - but it was worth it, even as an excercise for myself :) – Ethouris Jan 22 '15 at 11:20
  • 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. – Benji XVI Jul 04 '16 at 12:32
  • 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. – Ethouris Aug 09 '16 at 14:02
  • You don't need the `MatchingField` machinery, you can just use the type overload of [std::get](https://en.cppreference.com/w/cpp/utility/tuple/get), looking for `vector` – Caleth Aug 13 '18 at 23:33
  • This `MatchingField` machinery's purpose is exactly this "looking for". So I don't understand how `std::get` could be of any help here. – Ethouris Aug 20 '18 at 08:17
15

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;
}
shay
  • 755
  • 10
  • 22
  • I think that this requires that each element have a unique type. Is there a way around that? – Eyal Jul 31 '21 at 05:09
  • 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. – Eyal Jul 31 '21 at 05:23
  • @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 Feb 11 '22 at 07:06
  • 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: https://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. – Arda Feb 11 '22 at 23:16
5

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,
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • ah, I missed the extra constructor taking vector arguments. I was messing around with initializer lists of the `std::tuple>` and ran into the explicit nature of these constructors. – TemplateRex Jan 14 '15 at 12:10
  • It's a fun exercise but if I saw this in our production code I'd be looking to fire someone :-) – Richard Hodges Jan 14 '15 at 12:17
  • 1
    this type of exercise can have benefits, e.g. in data oriented design you are often looking at `std::tuple` instead of `std::vector` 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! – TemplateRex Jan 14 '15 at 12:19
  • @RichardHodges Good to know you aren't my boss ;)! I've got [something like this in production](http://stackoverflow.com/questions/18251815/creating-an-array-initializer-from-a-tuple-or-variadic-template-parameters). Unfortunately it's one of my most downvoted questions (where I think the DV's were just given as conciously chosen revenge). – πάντα ῥεῖ Jan 14 '15 at 13:37
  • Thanks! The code depends on c++14 features not available to my VS2013 though. Is something similar feasible with c++11 only? – user1101674 Jan 14 '15 at 14:50
  • @user1101674 writing your own `index_sequence_for` is easy, and should be easy to find using google. – Yakk - Adam Nevraumont Jan 14 '15 at 14:52
1

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'), ...);
L. F.
  • 19,445
  • 8
  • 48
  • 82
  • Dear @L. F. Could you explain more you solution? I have tried this but the program fails with a lot of errors: see here: https://godbolt.org/z/6h6q8h574 – Eugene W. May 16 '22 at 10:52
  • @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 :) – L. F. May 16 '22 at 11:03
0

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

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524