Как эмулировать направляющие вычеты для псевдонимов шаблона? - PullRequest
0 голосов
/ 13 января 2019

Рассмотрим следующее:

template <typename T, std::size_t N>
struct my_array
{
    T values[N];
};

Мы можем предоставить вычеты для my_array, что-то вроде

template <typename ... Ts>
my_array (Ts ...) -> my_array<std::common_type_t<Ts...>, sizeof...(Ts)>;

Теперь предположим, что my_array<T, 2> имеет какое-то особенное значение (но только то, что интерфейс и реализация остаются прежними), поэтому мы хотели бы дать ему более подходящее имя:

template <typename T>
using special = my_array<T, 2>;

Оказывается , что инструкции по выводу просто не работают для псевдонимов шаблонов, то есть это дает ошибку компиляции:

float x, y;

my_array a { x, y }; // works
special b { x, y }; // doesn't

Мы все еще можем сказать special<float> b и быть счастливыми. Однако предположим, что T - это какое-то длинное и утомительное имя типа, например, например. std::vector<std::pair<int, std::string>>::const_iterator. В этом случае будет очень удобно иметь здесь вывод аргументов шаблона. Итак, мой вопрос: если мы действительно хотим, чтобы special был типом, равным ( в некотором смысле ) my_array<T, 2>, и мы действительно хотим, чтобы направляющие вычеты (или что-то подобное) работали, как мы можем преодолеть это ограничение?

Заранее извиняюсь за несколько смутно поставленный вопрос.


Я придумал пару решений, оба с серьезными недостатками.

1) Сделать special отдельным не связанным классом с тем же интерфейсом, т.е.

template <typename T>
struct special
{
    T values[2];
};

template <typename T>
special (T, T) -> special<T>;

Это дублирование выглядит неловко. Более того, вместо написания таких функций, как

void foo (my_array<T, N>);

Я вынужден либо дублировать их

void foo (my_array<T, N>);
void foo (special<T>);

или делать

template <typename Array>
void foo (Array);

и полагаться, что интерфейс этих классов одинаков. Мне вообще не нравится этот вид кода (принимая что-либо и полагаясь исключительно на утку). Это может быть улучшено некоторыми SFINAE / концепциями, но это все еще кажется неудобным.

2) Сделать special функцией, т.е.

template <typename T>
auto special (T x, T y)
{
    return my_array { x, y };
}

Здесь нет дублирования типов, но теперь я не могу объявить переменную типа special, так как это функция, а не тип.

3) Оставьте special псевдоним шаблона, но предоставьте функцию make_special, похожую на C ++ 17:

template <typename T>
auto make_special (T x, T y)
{
    return my_array { x, y };
    // or return special<T> { x, y };
}

Это работает, в некоторой степени. Тем не менее, это не руководство по выводу, и смешивание классов, которые используют руководства по выводу с этой make_XXX функцией, звучит запутанно.

4) Как подсказывает @NathanOliver, заставить special наследовать от my_array:

template <typename T>
struct special : my_array<T, 2>
{};

Это позволяет нам предоставлять отдельные руководства по выводам для special, без дублирования кода. Однако может быть разумно написать такие функции, как

void foo (special<int>);

, который, к сожалению, не примет my_array<2>. Это можно исправить, предоставив оператор преобразования из my_array<2> в special, но это означает, что между ними есть циклические преобразования, что (по моему опыту) является кошмаром. Также special требуется дополнительные фигурные скобки при использовании инициализации списка.


Есть ли другие способы эмулировать нечто подобное?

1 Ответ

0 голосов
/ 14 января 2019

Простой ответ - дождаться C ++ 20. Вполне вероятно, что к тому времени дедукция шаблона класса научится просматривать шаблоны псевдонимов (а также агрегаты и унаследованные конструкторы, см. P1021 ).

<ч />

Кроме того, (1) определенно плохой выбор. Но выбор между (2), (3) и (4) во многом зависит от ваших вариантов использования. Обратите внимание, что для (4) направляющие вычетов также не наследуются до P1021, поэтому вам нужно будет скопировать направляющие вычетов базового класса, если вы пойдете по пути наследования.

Но есть и пятый вариант, который вы пропустили. Скучный:

special<float> b { x, y }; // works fine, even in C++11

Иногда этого достаточно?

...