Search code examples
c++c++17variadic-templatescompile-timestdtuple

How to convert a homogenous tuple to an array at compile time?


I want to preface this by stating that when I say homogenous tuple, I mean that either the type of every element is the same, or every element has a public member which is the same type across all elements. In this case the public member is also required to be static constexpr.

This is a simplified piece of code describing my issue:

#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

template <std::size_t N>
struct Int : std::integral_constant<std::size_t, N>
{
    Int() = default;
    Int(const Int&) = delete;
    Int(Int&&) = delete;

    Int& operator=(const Int&) = delete;
    Int& operator=(Int&&) = delete;
};

template <std::size_t... Ns>
struct Stuff
{
    using type = std::tuple<Int<Ns>...>;
    using arr_type = std::array<std::size_t, std::tuple_size_v<type>>;
    static constexpr arr_type arr = {Ns...};
};

I'm fairly sure it's correct, with the only issue being the last line, but hopefully you get the gist.

Right now, the array is constructed using Ns.... I want to be able to construct the array from type instead (the reason for this is that type has had duplicate types removed from the tuple (based on .value) and has also had the types sorted (based on .value) in the actual version of this code). If instead it's easier to sort and remove duplicate values from the array at compile time, then that will also be accepted as an answer.

So ideally I would write some sort of templated struct to create the array (as in the struct would have a static constexpr arr_type value = member), and in the Stuff struct, I would initialize the arr member like this:

static constexpr arr_type arr = detail::make_array_v<type>;

I'm willing to lift the restrictions on copy construction/assignment for the Int struct if absolutely necessary. I'm not sure how to write a helper function/struct for this, and any help is appreciated. The answer can use any version of c++ up to and including c++20.


Solution

  • I think this solves your problem in a somehow generic way:

    namespace detail {
    
        template <class Tuple>
        struct make_array;
    
        template <class V, template <V N> class C, V... Ns>
        struct make_array<std::tuple<C<Ns>... >> {
            static constexpr std::array value{Ns... };
        };
    
        template <class Tuple>
        constexpr auto make_array_v = make_array<Tuple>::value;
    
    }
    

    It is basically a template with a std::tuple specialization for std::tuple of a given template argument with a list of values.

    Here are possible usages (assuming Char is similar to your Int bur for char):

    constexpr auto arr1 = detail::make_array_v<std::tuple<Int<42>, Int<38>, Int<57>>>;
    static_assert(arr1 == std::array<std::size_t, 3>{42, 38, 57});
    
    constexpr auto arr2 = detail::make_array_v<std::tuple<Char<'A'>, Char<'Z'>,
                                                          Char<'w'>, Char<'U'>>>;
    static_assert(arr2 == std::array{'A', 'Z', 'w', 'U'});
    

    The implementation is valid for C++17, and I think quite easy to convert for C++14 (you just have to specify the template arguments for value). The tests above require C++20 for the constexpr comparison operator for std::array.


    Full godbolt.org code with your example: https://godbolt.org/z/jkiA9R