45

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!

5
  • 2
    You can forward the argument types to a std::tuple. Commented Jan 14, 2015 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.] Commented Jan 14, 2015 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. Commented Jan 14, 2015 at 11:36
  • If only one of the vectors will be populated at any one time, why not use a boost::variant of vectors? Or will more than one be populated at a time? Commented Jan 14, 2015 at 14:39
  • See also: stackoverflow.com/a/13462578/1599699
    – Andrew
    Commented May 14, 2018 at 20:12

5 Answers 5

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>’
8
  • 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::reference_wrapper<unsigned int>>,std::allocator<std::reference_wrapper<std::reference_wrapper<unsigned int>>>>>>' being compiled ... Commented Jan 15, 2015 at 13:42
  • 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.
    – Ethouris
    Commented Jan 20, 2015 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. Commented Jan 22, 2015 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
    Commented Jan 22, 2015 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
    Commented Jul 4, 2016 at 12:32
17

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;
}
4
  • I think that this requires that each element have a unique type. Is there a way around that?
    – Eyal
    Commented Jul 31, 2021 at 5: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
    Commented Jul 31, 2021 at 5: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
    Commented Feb 11, 2022 at 7: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: 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
    Commented Feb 11, 2022 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,
6
  • 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. Commented Jan 14, 2015 at 12:10
  • It's a fun exercise but if I saw this in our production code I'd be looking to fire someone :-) Commented Jan 14, 2015 at 12:17
  • 1
    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! Commented Jan 14, 2015 at 12:19
  • @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). Commented Jan 14, 2015 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? Commented Jan 14, 2015 at 14:50
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'), ...);
2
  • 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
    – Eugene W.
    Commented May 16, 2022 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.
    Commented May 16, 2022 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).

Not the answer you're looking for? Browse other questions tagged or ask your own question.