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.

0

1 Answer 1

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

Sign up to request clarification or add additional context in comments.

7 Comments

This is wonderful. I didn't understand the significance of embedding values in types until this.
One can use std::make_index_sequence<std::size(container().containee)> instead of std::make_index_sequence<3>.
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?
@dfri: Interesting, it deserves a follow up question. Do you want to open one, or should I do it?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.