Expanding a constexpr array into a set of non-type template parameters
Asked Answered
F

1

10

Suppose I have a compile-time constexpr array and a variadic class template with a set of non-type parameters of the same type as the elements of the array.

My objective is to instantiate the class template with the values from the array:

struct Container
{
    int containee[3];
};

constexpr Container makeContainer();

template <int... Elements> class Foo;

Foo<makeContainer().containee[0],
    makeContainer().containee[1],
    makeContainer().containee[2]> foo;

The above code works well. However, I'm quite unhappy about having to manually index the array whenever I need to instantiate the Foo template. I would like the compiler to do that for me automatically:

Foo<Magic(makeContainer().containee)> foo;

I did some RTFM at cppreference, but that didn't help. I'm aware of std::forward<>(), but it cannot be applied to template argument lists.

Furore answered 13/11, 2017 at 11:45 Comment(0)
L
8
  1. Change makeContainer to a struct with a constexpr operator() or a constexpr lambda (C++17). A function pointer will not work here.

    struct makeContainer
    {
        constexpr auto operator()() const
        {
            return Container{/* ... */};
        }
    };
    
  2. Use std::make_index_sequence and std::index_sequence to generate a compile-time sequence of the indices:

    template <typename C>
    constexpr auto fooFromContainer(const C& container)
    {
        return fooFromContainerImpl(container, std::make_index_sequence<3>{});
    }
    
  3. Create a new constexpr container instance through C, then expand the sequence to index the elements in a constant expression:

    template <typename C, std::size_t... Is>
    constexpr auto fooFromContainerImpl(const C& container, std::index_sequence<Is...>)
    {
        constexpr auto c = container();
        return Foo<c.containee[Is]...>{};
    }
    

complete example on wandbox.org


Just for fun, here's a C++20 implementation:

struct container { int _data[3]; };

template <int... Is> 
struct foo 
{ 
    constexpr auto t() const { return std::tuple{Is...}; }
};

template <typename C>
constexpr auto foo_from_container(const C& c)
{
    return []<std::size_t... Is>(const auto& c, std::index_sequence<Is...>)
    {
        return foo<c()._data[Is]...>{};
    }(c, std::make_index_sequence<3>{});
}

int main()
{
    constexpr auto r = foo_from_container([]{ return container{42, 43, 44}; });   
    static_assert(r.t() == std::tuple{42, 43, 44});
}

live example on wandbox.org

Legation answered 13/11, 2017 at 12:17 Comment(7)
This is wonderful. I didn't understand the significance of embedding values in types until this.Tamandua
One can use std::make_index_sequence<std::size(container().containee)> instead of std::make_index_sequence<3>.Landsturm
None of these examples compiles anymore. The top one fails with "error: 'container' is not a constant expression" (3rd snippet) and the bottom one with "error: 'c' is not a constant expression" (in return foo<c()._data[Is]...>{};). Was this ever legal C++20, or has the HEAD for gcc and clang at wandbox both subtly changed into non-compliance of C++2a vs C++20?Adamite
@dfri: Interesting, it deserves a follow up question. Do you want to open one, or should I do it?Legation
Feel free to open one, you can likely better describe the underlying standard (draft) sections that these approaches were based (back in 2017).Adamite
@dfri: posted here: #60455362Legation
I guess based on the answers and comments to your question, gcc and clang (HEAD) is correct in rejecting these snippets, as neither container in fooFromContainerImpl() (first snippet) nor c (second snippet) in foo_from_container are constant expressions.Adamite

© 2022 - 2024 — McMap. All rights reserved.