Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to emulate deduction guides for template aliases?

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?

like image 751
lisyarus Avatar asked Jan 13 '19 20:01

lisyarus


People also ask

What is a deduction guide?

Template deduction guides are patterns associated with a template class that tell the compiler how to translate a set of constructor arguments (and their types) into template parameters for the class. The simplest example is that of std::vector and its constructor that takes an iterator pair.

What is an alias template?

Alias templates are a way to give a name to a family of types. Template parameters can be types, non-types, and templates themselves.

Can a deduction guide be a member of a class template?

User-defined deduction guides must name a class template and must be introduced within the same semantic scope of the class template (which could be namespace or enclosing class) and, for a member class template, must have the same access, but deduction guides do not become members of that scope.

What is the use of a template deduction?

Template argument deduction is used when selecting user-defined conversion function template arguments. A is the type that is required as the result of the conversion. P is the return type of the conversion function template, except that a) if the return type is a reference type then P is the referred type;

Does using a member typedef or alias template render the parameter?

Using a member typedef or alias template in a constructor or constructor template's parameter list does not, by itself, render the corresponding parameter of the implicitly generated guide a non-deduced context.

When is class template argument deduction not performed?

Class template argument deduction is only performed if no template argument list is present. If a template argument list is specified, deduction does not take place.


1 Answers

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?

like image 149
Barry Avatar answered Oct 17 '22 05:10

Barry