Produce std::tuple of same type in compile time given its length by a template argument
Asked Answered
C

6

15

In c++, how can I implement a function with an int template argument indicating the tuple length and produce a std::tuple with that length?

E.g.

func<2>() returns std::tuple<int, int>();
func<5>() returns std::tuple<int, int, int, int, int>().
Caespitose answered 11/8, 2016 at 0:8 Comment(5)
What did you try so far?Arsphenamine
obligatory: have you considered just using a std::array? You can use an array much like a tuple (with std::tuple_size, std::get, etc) and would simplify this a lot.Nitrochloroform
@user2079303, my intuition is we keep a list of types, count down from N to 0 and add int to the list when counter is decremented. When N is 0, returns that tuple with the type list. But I don't know how to carry it out.Caespitose
@RyanHaining, I am calling a function that only accepts std::tuple.Caespitose
@Fake I don't know if you're the author of that function, but generally using a tuple as a concrete type (ie: not in a generic context) is bad design. If you can, try to redesign the function to accept a "TupleLike" concept that supports std::get and such.Crank
B
17

Here is a recursive solution with alias template and it's implementable in C++11:

template <size_t I,typename T> 
struct tuple_n{
    template< typename...Args> using type = typename tuple_n<I-1, T>::template type<T, Args...>;
};

template <typename T> 
struct tuple_n<0, T> {
    template<typename...Args> using type = std::tuple<Args...>;   
};
template <size_t I,typename T>  using tuple_of = typename tuple_n<I,T>::template type<>;

For example if we want "tuple of 3 doubles" we can write:

tuple_of<3, double> t;
Bony answered 11/8, 2016 at 10:43 Comment(1)
This is actually better than Ryan's solution IMO, even for C++14, because it's more generically useful - for example, you can declare variables with it.Durr
N
9

Using an index_sequence and a helper type alias you can generate the type you want:

// Just something to take a size_t and give the type `int`
template <std::size_t>
using Integer = int;

// will get a sequence of Is = 0, 1, ..., N
template <std::size_t... Is>
auto func_impl(std::index_sequence<Is...>) {
    // Integer<Is>... becomes one `int` for each element in Is...
    return std::tuple<Integer<Is>...>{};
}

template <std::size_t N>
auto func() {
    return func_impl(std::make_index_sequence<N>{});
}

It is worth calling out that in the general case you would probably be better with a std::array, (in your case you can't use one), but a std::array can behave like a tuple, similarly to a std::pair.

Update: since you've made it clear you're working with c++11 and not 14+, you'll need to get an implementation of index_sequence and related from somewhere (here is libc++'s). Here is the C++11 version of func and func_impl with explicit return types:

template <std::size_t... Is>
auto func_impl(std::index_sequence<Is...>) -> std::tuple<Integer<Is>...> {
  return std::tuple<Integer<Is>...>{};
}

template <std::size_t N>
auto func() -> decltype(func_impl(std::make_index_sequence<N>{})) {
  return func_impl(std::make_index_sequence<N>{});
}
Nitrochloroform answered 11/8, 2016 at 0:18 Comment(6)
Note that this is C++14.Wilfordwilfred
Is it possible to implement with c++11?Caespitose
@Fake yeah if you can get an implementation of index_sequence and then add explicit return types to the functions. Should your question be tagged c++11?Nitrochloroform
@RyanHaining, added the tag. Sorry, I thought c++14 is excluded by default.Caespitose
Do you know how I can make the return type explicit?Caespitose
@Fake we are less than a year away from C++17 being included by default. :pWilfordwilfred
A
5

The plain old recursion is your friend:

template<std::size_t N>
auto array_tuple() {
    return std::tuple_cat(std::tuple<int>{}, array_tuple<N-1>());
}

template<>
auto array_tuple<0>() {
    return std::tuple<>{};
}
Arsphenamine answered 11/8, 2016 at 0:26 Comment(1)
It may be useful to specify C++14 standard is needed, for auto return type of function.Mcadoo
C
2

If you're okay with a C++14 solution, Ryan's answer is the way to go.

With C++11, you can do the following (still based on index_sequence, but that's implementable in C++11):

template <size_t N, class T, class = std::make_index_sequence<N>>
struct n_tuple;

template <size_t N, class T, size_t... Is>
struct n_tuple<N, T, std::index_sequence<Is...>> {
    template <size_t >
    using ignore = T;

    using type = std::tuple<ignore<Is>...>;
};

template <size_t N, class T>
using n_tuple_t = typename n_tuple<N, T>::type;

With that:

template <size_t N>
n_tuple_t<N, int> func() {
    return n_tuple_t<N, int>{};
}
Cavicorn answered 11/8, 2016 at 1:7 Comment(3)
Seems the index_sequence is still a c++14 feature. Is it possible to not depend on that as well?Caespitose
Probably I can just copy over the index_sequence implementation.Caespitose
@Fake Yes, as I stated in the answer, it's implementable in C++11Cavicorn
I
1

Here are two boost.hana solutions (C++14):

//first
hana::replicate<hana::tuple_tag>(int{}, hana::size_c<2>);

//second
hana::cycle(std::make_tuple(int{}), hana::size_c<2>);

Both produce integer-tuples of size 2, but instead of std::tuples they yield hana::tuples.

Intercellular answered 23/12, 2018 at 21:14 Comment(0)
T
0

If for some reason you really want a tuple instead of an array, you may use std::tuple_cat on an array. I think this method is the best, because it does not require any third-party libraries nor even writing any template metaprogramming code by yourself.

std::array<int, 3> arr;
auto tup = std::tuple_cat(arr);
static_assert(std::is_same_v<decltype(tup), std::tuple<int, int, int>>);
Transcendent answered 25/4, 2023 at 16:47 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.