How to emulate deduction guides for template aliases?
Asked Answered
L

1

10

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?

Loveinidleness answered 13/1, 2019 at 20:44 Comment(5)
Have special inherit from array?Rasure
@Rasure I didn't consider inheritance because I generally prefer not to create class hierarchies if I can. Now that I'm thinking about it, this might actually be the best option. Thank you. Why not make it an answer?Loveinidleness
@Rasure I've added some thoughts on using inheritance.Loveinidleness
I am increasingly convinced that C++ needs to differentiate between strong and weak type aliases.Endaendall
See Class template argument deduction not working with alias template, which has an answer from the Editor that cites changes making CTAD via alias templates doable in C++20.Warmblooded
B
5

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?

Breeze answered 14/1, 2019 at 0:49 Comment(8)
Thank you for pointing at P1021, I'll have a thorough look at it. As for your suggestion, I don't quite get it - it just gives up on the problem instead of solving it. I mean, it is surely an option, the default one.Loveinidleness
@Loveinidleness I mean, the goal is to construct b right? If you have a choice between all these workarounds and just providing a template parameter... sometimes just providing a template parameter is the simplest.Breeze
Unfortunately, in my case T is come long tedious-to-type thing, that's why I stated that I really want type deduction here. I apologize for not making this clear in the question.Loveinidleness
@Loveinidleness So yeah, it really depends on your usage between the 2/3 route and the 4 route. I don't know that I can definitively state which is better. (3) is probably going to be the most recognizable to people, since that's what we've had to do up until CTAD came around anyway?Breeze
@Loveinidleness what about a typedef T EasyToType and then use special<EasyToType> ?Towhead
Considering understanding by others you are probably right. However, this is a code in a pet project of mine which will hardly ever be seen by anybody, so I guess I can just wait for the C++20 :)Loveinidleness
@Towhead This surely helps, but the whole idea (or hope?) was to make this unnecessary !Loveinidleness
I guess all possibilities have been mentioned by now. I'll accept the answer. Thank you again.Loveinidleness

© 2022 - 2024 — McMap. All rights reserved.