Search code examples
c++templatesc++17template-templates

Generic template template parameters


Fed up with "uniform initialisation" not being very uniform, I decided to write a generic construct() wrapper which uses aggregate initialisation if a type is an aggregate, and direct initialisation otherwise:

template <class T, class... Args,
          std::enable_if_t<std::is_aggregate_v<T>, int> = 0>
constexpr auto construct(Args&&... args)
    -> decltype(T{std::forward<Args>(args)...})
{
    return T{std::forward<Args>(args)...};
}

template <class T, class... Args>
constexpr auto construct(Args&&... args)
   -> decltype(T(std::forward<Args>(args)...))
{
    return T(std::forward<Args>(args)...);
}

This works well enough:

template <class T, class U>
struct my_pair { T first; U second; };

auto p = construct<my_pair<int, float>>(1, 3.14f);
auto v = construct<std::vector<int>>(5, 0);

I'd like to extend this to use template argument deduction for constructors. So I added another pair of overloads:

template <template <class...> class T, // <-- (1) 
          class... Args,
          class A = decltype(T{std::declval<Args>()...}),
          std::enable_if_t<std::is_aggregate_v<A>, int> = 0>
constexpr auto construct(Args&&... args)
    -> decltype(T{std::forward<Args>(args)...})
{
     return T{std::forward<Args>(args)...};
}

template <template <class...> class T, // <-- (1)
          class... Args>
constexpr auto construct(Args&&... args)
    -> decltype(T(std::forward<Args>(args)...))
{
    return T(std::forward<Args>(args)...);
}

Perhaps surprisingly (at least to me), this works for simple cases:

// deduction guide for my_pair
template <class T, class U> my_pair(T&&, U&&) -> my_pair<T, U>;

auto p = construct<my_pair>(1, 3.14f); // my_pair<int, float>
auto v = construct<std::vector>(5, 0); // vector of 5 ints

Unfortunately however this fails when trying to call

auto a = construct<std::array>(1, 2, 3); // No matching call to construct()

because std::array has a non-type template parameter, so it doesn't match the template <class...> class T template template parameter at (1).

So my question is, is there a way to formulate the parameter at (1) such that it can accept any class template name, regardless of the kind (type or non-type) of its template parameters?


Solution

  • Unfortunately, there is no proper way of doing this without code repetition. The newly added "auto as template parameter" in C++17 only supports non-type template parameters.

    The only way I can think this could work is by using a code generator to generate a fixed amount of permutations of auto and class. E.g.

    template <
        template <class, auto...> class T,
        class... Args>
    constexpr auto construct(Args&&... args) // ...
    
    template <
        template <class, auto, class...> class T,
        class... Args>
    constexpr auto construct(Args&&... args) // ...
    
    template <
        template <auto, class, auto...> class T,
        class... Args>
    constexpr auto construct(Args&&... args) // ...
    
    template <
        template <auto, class, auto, class...> class T,
        class... Args>
    constexpr auto construct(Args&&... args) // ...
    
    template <
        template <auto, class, auto, class, auto...> class T,
        class... Args>
    constexpr auto construct(Args&&... args) // ...
    
    // and so on...
    

    live example on wandbox

    Sounds like you have a good idea for a proposal...