How to deduce parameter-pack from brace-enclosed initializer list?

72 Views Asked by At

This is a convenience question.

I have a valid implementation of FooImp< Aux<int...>, Aux<int...> > in the namespace FooDom when both int... parameter packs are of same length.

My goal is being able to use the short-hand Foo<{1,2,3},{4,5,6}> for the verbose form FooDom::FooImp< FooDom::Aux<1,2,3>, FooDom::Aux<4,5,6> >. Therein, 1,2,3 and 4,5,6 are examples.

namespace FooDom{
   template<int...> struct Aux{};
   template<typename,typename> class FooImp;
};

template<int ...u, int ...v>
using Foo<{u...},{v...}> = FooDom::FooImp< FooDom::Aux<u...>, FooDom::Aux<v...> >;

Question: How can I define the short-hand c++20-compliantly*?

*) I am aware or have read that brace-enclosed template initializers are not standard-compliant, yet all compilers succeed at compiling them. For the given project, the agreed standard is -std=c++20, so this is the version in which it should compile.

What I tried

  1. A using assignment, as presented in the above minimum example. The GNU G++ 12.2.0 error message with -std=c++20 is error: template parameters not deducible in partial specialization: note:'u' note:'v'

  2. Inheritance as per the following replacement:

template<auto,auto> class Foo;

template<int ...u, int ...v>
class Foo<{u...},{v...}>: public FooDom::FooImp< FooDom::Aux<u...>, FooDom::Aux<v...> >{};

But auto{} is not c++20 compliant. So I compile with -std=c++2b. Then, the same error message template parameters not deducible in partial specialization reappears.

1

There are 1 best solutions below

0
Jan Schultke On

Firstly, note that initializer lists are a non-deduced context (What is a non-deduced context?) and auto cannot be deduced from {...} generally. There is an exception for placeholder variables like

auto l = {1, 2, 3; // l is of type std::initializer_list<int>

... however, it's not possible for template parameters or function parameters.

You can use class template argument deduction to write the following though:

#include <array>
#include <concepts>

template<std::array A, std::array B>
  requires std::same_as<typename decltype(A)::value_type, int>
        && std::same_as<typename decltype(B)::value_type, int>   
struct Foo {};

using F = Foo<{1, 2, 3}, {4, 5, 6}>;

std::array makes this somewhat fragile because there is no partial deduction, and you cannot have std::array<int> parameters that only deduce the size. However, you could make such a type yourself.

Getting the individual ints out of the array is also possible:

template <std::array Array>
  requires std::convertible_to<Array::value_type, int>
using expand_t = decltype([]<std::size_t... I>(std::index_sequence<I...>) {
    return Aux<Array[I]...>{};
}(std::make_index_sequence<Array.size()>));

Foo then becomes:

template // ...
struct Foo : FooImp<expand_t<A>, expand_t<B>> {};