Consider the following:
template <typename T, std::size_t N>
struct my_array
{
T values[N];
};
We can provide deduction guides for my_array
, something like
template <typename ... Ts>
my_array (Ts ...) -> my_array<std::common_type_t<Ts...>, sizeof...(Ts)>;
Now, suppose that my_array<T, 2>
has some very special meaning (but only meaning, the interface & implementation stay the same), so that we'd like to give it a more suitable name:
template <typename T>
using special = my_array<T, 2>;
It turns out that deduction guides simply don't work for template aliases, i.e. this gives a compilation error:
float x, y;
my_array a { x, y }; // works
special b { x, y }; // doesn't
We can still say special<float> b
and be happy. However, suppose that T
is some long and tedious type name, like e.g. std::vector<std::pair<int, std::string>>::const_iterator
. In this case it will be extremely handy to have template argument deduction here. So, my question is: if we really want special
to be a type equal (in some sense) to my_array<T, 2>
, and we really want deduction guides (or something similar) to work, how can one overcome this limitation?
I apologize in advance for a somewhat vaguely posed question.
I've come up with a couple of solutions, both with serious disadvantages.
1) Make special
a separate unrelated class with the same interface, i.e.
template <typename T>
struct special
{
T values[2];
};
template <typename T>
special (T, T) -> special<T>;
This duplication looks awkward. Furthemore, instead of writing functions like
void foo (my_array<T, N>);
I'm forced either to duplicate them
void foo (my_array<T, N>);
void foo (special<T>);
or to do
template <typename Array>
void foo (Array);
and rely on the interface of this classes being the same. I don't generally like this kind of code (accepting anything and relying solely on duck typing). It can be improved by some SFINAE/concepts, but this still feels awkward.
2) Make special
a function, i.e.
template <typename T>
auto special (T x, T y)
{
return my_array { x, y };
}
No type duplication here, but now I cannot declare a variable of type special
, since it is a function, not a type.
3) Leave special
a template alias, but provide a pre-C++17-like make_special
function:
template <typename T>
auto make_special (T x, T y)
{
return my_array { x, y };
// or return special<T> { x, y };
}
It works, to some extent. Still, this is not a deduction guide, and mixing classes that use deduction guides with this make_XXX
functions sounds confusing.
4) As suggested by @NathanOliver, make special
inherit from my_array
:
template <typename T>
struct special : my_array<T, 2>
{};
This enables us to provide separate deduction guides for special
, and no code duplication involved. However, it may be reasonable to write functions like
void foo (special<int>);
which unfortunately will fail to accept my_array<2>
. It can be fixed by providing a conversion operator from my_array<2>
to special
, but this means we have circular conversions between these, which (in my experience) is a nightmare. Also special
needs extra curly braces when using list initialization.
Are there any other ways to emulate something similar?