Search code examples
c++templatesvariadic-templatestemplate-meta-programmingconstexpr

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


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.


Solution

    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