问题
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?
回答1:
The easy answer is to wait until C++20. It's pretty likely that class template deduction will be learn how to look through alias templates by then (as well as aggregates and inherited constructors, see P1021).
Beyond that, (1) is definitely a bad choice. But choosing between (2), (3), and (4) largely depends on your use cases. Note that for (4), deduction guides aren't inherited until P1021 either, so you'd need to copy the base class' deduction guides if you go the inheritance route.
But there's also a fifth option that you missed. The boring one:
special<float> b { x, y }; // works fine, even in C++11
Sometimes, that's good enough?
来源:https://stackoverflow.com/questions/54173048/how-to-emulate-deduction-guides-for-template-aliases