How to create a boost::tuple with the specified number of elements (of the same type)?

1k views Asked by At

Assume that I have the following class definition:

template <unsigned int N>
class foo
{
    boost::tuples::tuple<...> bar;
};

Given the compile-time constant N, I would like to expand the type of bar to be a tuple that holds N elements of a specified type. That is, the type of foo<2>::bar would be boost::tuples::tuple<T, T>. I'm guessing that I can use Boost.MPL for this, but I haven't figured out the exact sequence yet. I think I could do:

template <typename T, int N>
struct type_repeater
{
    typedef typename boost::mpl::fold<
        boost::mpl::range_c<T, 0, N>,
        boost::mpl::vector<>,
        boost::mpl::push_back<_1, T>
    >::type type;
};

So then for instance type_repeater<T, 2>::type would be equivalent to boost::mpl::vector<T, T>. I'm just not sure how/if I can take that type list and inject it into the argument list of a tuple, like I want. Is this possible?

3

There are 3 answers

0
mpark On BEST ANSWER

Although this is totally doable with variadic templates and std::tuple, the best solution for what you want I think is to just use std::array. If you simply want a container with N instances of T, the std::array signature is already template <typename T, std::size_t N> class array. I think it fits your need exactly.

Having said that, if you really want std::tuple for some reason you can do it like so:

#include <tuple>

/* Forward declaration. */
template <std::size_t N, typename T>
class Tuple;

/* Convenience type alias. */
template <std::size_t N, typename T>
using TTuple = typename Tuple<N, T>::type;

/* Base case. */
template <typename T>
class Tuple<0, T> {
  public:

  using type = std::tuple<>;

}; // Tuple<0>

/* Recursive case. */
template <std::size_t N, typename T>
class Tuple {
  public:

  /* Note the use of std::declval<> here. */
  using type = decltype(std::tuple_cat(std::declval<std::tuple<T>>(),
                                       std::declval<TTuple<N - 1, T>>()));

};  // Tuple<N, T>

/* std::declval<> is necessary to support non default constructable classes. */
class NoDefault {
  public:

  NoDefault() = delete;

};  // Foo

/* Sample use. */
static_assert(std::is_same<TTuple<2, NoDefault>,
                           std::tuple<NoDefault, NoDefault>>::value, "");

int main() {}

Note: If you don't have access to C++11 but do have access to boost, boost::array and boost::tuples::tuple will do fine in lieu of std::array and std::tuple.

4
OmnipotentEntity On

This seems to be a good minimal example using C++11

#include <tuple>
template <unsigned int N, typename T>
struct type_repeater {
  typedef decltype(std::tuple_cat(std::tuple<T>(), typename type_repeater<N-1, T>::type())) type;
};

template <typename T>
struct type_repeater<0, T> {
  typedef decltype(std::tuple<>()) type;
};

int main() {
  type_repeater<3, float>::type asdf;
  std::get<0>(asdf);
  std::get<1>(asdf);
  std::get<2>(asdf);
}
0
Madera On

Since you explicitly ask for a way to make mpl::vector into a runtime container, I recommend you stay Boosty and use Fusion's as_vector:

Given your initial example where you mol::fold to get an mpl::vector, you would then use:

boost::fusion::result_of::as_vector<
    mpl::vector<T, T>
>::type;

To get a Fusion Vector, which seems to be what you want. Boost Fusion fills the gap between the compile-time and runtime world.

Also, this is pre C++11, which is still important in many (maybe most?) projects.