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?
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...
Sounds like you have a good idea for a proposal...