9

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.

Pavel Kirienko
  • 1,162
  • 1
  • 15
  • 31

1 Answers1

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

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 1
    This is wonderful. I didn't understand the significance of embedding values in types until this. – Passer By Nov 13 '17 at 12:23
  • 2
    One can use `std::make_index_sequence` instead of `std::make_index_sequence<3>`. – Constructor Nov 13 '17 at 12:38
  • 1
    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{};`). 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? – dfrib Feb 28 '20 at 12:43
  • @dfri: Interesting, it deserves a follow up question. Do you want to open one, or should I do it? – Vittorio Romeo Feb 28 '20 at 14:10
  • Feel free to open one, you can likely better describe the underlying standard (draft) sections that these approaches were based (back in 2017). – dfrib Feb 28 '20 at 14:18
  • 1
    @dfri: posted here: https://stackoverflow.com/questions/60454862/invoking-constexpr-member-function-through-reference-clang-vs-gcc – Vittorio Romeo Feb 28 '20 at 15:35
  • 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. – dfrib Mar 02 '20 at 12:07