Как написать руководство по дедукции для псевдонимов агрегатных шаблонов?

С помощью C++20 возможно создание руководящих принципов вывода для шаблона псевдонима (См. Раздел "Вывод для шаблонов псевдонимов" на https://en.cppreference.com/w/cpp/language/class_template_argument_deduction). Тем не менее, я не мог заставить их работать с синтаксисом агрегированной инициализации. Похоже, в этом случае руководство по вычету псевдонима не создается.

См. Этот пример:

#include <array>

template <size_t N>
using mytype = std::array<int, N>;

// Deduction guideline ???

int main() {
    // mytype error_object = {1, 4, 7}; // ERROR
    mytype<3> object = {1, 4, 7}; // OK, but I have to manually specify the size.
    return object[0];
}

Я пробовал писать правила дедукции, но каждый раз получаю ошибки компилятора.

template <typename T, typename ... U>
mytype(T, U...) -> mytype<1+sizeof...(U)>; // Compiler error

и любые другие рекомендации, которые я мог придумать.

Возможно ли автоматическое определение размера псевдонима массива?

Я использую GCC 10.2

1 ответ

Решение

Возможно ли автоматическое определение размера псевдонима массива?

Я считаю, что это должно быть возможно при реализации, соответствующей стандарту. Вам не нужно (и нельзя) добавлять дополнительные направляющие.

Однако GCC реализует другой набор правил, чем тот, который определяет стандарт:

        This implementation differs from [the specification] in two significant ways:

1) We include all template parameters of A, not just some.
2) The added constraint is same_type instead of deducible.

Разработчик полагал, что "это упрощение должно иметь такой же эффект для реального использования". Но, очевидно, это не так: эта реализация не работает в вашем случае, а ICE - в некоторых других случаях.


Для справки, я постараюсь следовать стандарту и покажу, как руководство для mytype генерируется.

У нас есть это объявление шаблона псевдонима (шаблон псевдонима называется A в стандарте):

       template <size_t N>
using mytype = std::array<int, N>;

и это руководство по выводам из стандартной библиотеки ( [array.cons]):

       template<class T, class... U>
array(T, U...) -> array<T, 1 + sizeof...(U)>;

Во-первых, шаблон функции (называемый fв стандарте) создается из руководства по вычетам ( http://eel.is/c++draft/over.match.class.deduct#1):

       template<class T, class... U>
auto f(T, U...) -> array<T, 1 + sizeof...(U)>;

Затем в [over.match.class.deduct] / 2:

аргументы шаблона возвращаемого типа fвыведены из определяющего-типа-идентификатор из A в соответствии с процессом в [temp.deduct.type], за исключением того, что вывод не завершится неудачно, если не все аргументы шаблона выведены.

То есть мы выводим аргументы шаблона в array<T, 1 + sizeof...(U)> из std::array<int, N>. В этом процессе T выводится как int; U не выводима, поэтому остается как есть.

Результат вычитания подставляется в шаблон функции, в результате чего:

       template<class T, class... U>
auto g(int, U...) -> array<int, 1 + sizeof...(U)>;

Затем мы генерируем шаблон функции f'. f' имеет тот же тип возвращаемого значения и типы параметров функции, что и g. (Если f имеет особые свойства, они наследуются f'.) Но особенно, список параметров шаблона f'состоит из ([over.match.class.deduct] / (2.2), выделено мной):

все параметры шаблона A(включая их аргументы шаблона по умолчанию), которые появляются в приведенных выше выводах или (рекурсивно) в их аргументах шаблона по умолчанию, за которыми следуют параметры шаблона f которые не были выведены (включая их аргументы шаблона по умолчанию), в противном случае f' не является шаблоном функции.

поскольку Nне отображается в выводе, он не входит в список параметров шаблона (в этом GCC отличается от стандарта).

Дополнительно, f'имеет ограничение ([over.match.class.deduct] / (2.3)):

это выполняется тогда и только тогда, когда аргументы A выводимы (см. ниже) из возвращаемого типа.

Поэтому по стандарту сгенерированный шаблон функции выглядит так:

       template<class... U>
  requires deducible<array<int, 1 + sizeof...(U)>>
auto f'(int, U...) -> array<int, 1 + sizeof...(U)>;

Ясно, что размер можно вывести как 1 + sizeof...(U) согласно этому руководству.

На следующем шаге посмотрим, как deducible определено.

http://eel.is/c++draft/over.match.class.deduct#3:

Аргументы шаблона A называются выводимыми из типа T если, учитывая шаблон класса

        template <typename> class AA;

с одной частичной специализацией, список параметров шаблона которой соответствует A и чей список аргументов шаблона является специализацией A со списком аргументов шаблона A ([temp.dep.type]), AA<T> соответствует частичной специализации.

В нашем случае частичная специализация будет:

        template <size_t N> class AA<mytype<N>> {};

Так deducible может быть объявлен как:

        template <class T> concept deducible = requires { sizeof(AA<T>); };

поскольку N выводится из 1 + sizeof...(U), array<int, 1 + sizeof...(U)> всегда допустимое совпадение для mytype<N> (он же std::arrray<int, N>), и, следовательно, ограничение deducible<array<int, 1 + sizeof...(U)>> всегда доволен.

Следовательно, согласно стандарту, сгенерированное руководство является жизнеспособным и может определять размер.

Для сравнения, GCC генерирует:

       template<class... U, size_t N>
  requires same_type<array<int, 1 + sizeof...(U)>, mytype<N>>
auto f_(int, U...) -> array<int, 1 + sizeof...(U)>;

... который не может вывести N.

Другие вопросы по тегам