Код ниже компилируется с использованием gcc 14.1.1, но clang 18.1.8 его не принимает. Какой из них правильный и есть ли обходной путь?
template <template <typename...> class T>
struct s {
template <typename... U>
using type = T<U...>;
};
/* the following definition of c and a are defined by the users of my library; they cannot be part of the workaround */
template <typename T>
class c {};
template <typename T>
using a = c<T>;
/* end of "user-defined" part */
int main()
{
typename s<c>::type<int>{};
typename s<a>::type<int>{}; // error, see bellow
}
❯ clang++ -Wall -std=c++23 -pedantic text.cpp -o test
test.cpp:7:20: error: pack expansion used as argument for non-pack parameter of alias template
7 | using type = T<U...>;
| ^~~~
Если сделать шаблон псевдонима переменным, он будет компилироваться с clang, но псевдонимы шаблонов создаются пользователями, и библиотека не имеет над ними контроля (см. код).
Случай, описанный в разделах Распаковка пакетов параметров в псевдонимах шаблона и Расширение пакета для шаблона псевдонима завершается сбоем как с GCC, так и с clang. Однако может случиться так, что основная причина та же, и предоставленное решение использует механизм, аналогичный ответу здесь. Код в этом вопросе работает с GCC и не работает только с clang. Возможно, это связано с использованием аргументов шаблона шаблона.
Случай, описанный в разделе Обходной путь передачи пакета параметров в шаблоны псевдонимов (которые имеют параметры, не являющиеся пакетами) чем-то похож на этот вопрос (за исключением аргумента шаблона шаблона), но данный ответ к этому не применим. Как я описал выше, псевдонимы определяются в пользовательском коде, и библиотека не имеет над ними контроля.
Пожалуйста, проведите тщательное исследование, прежде чем задавать какие-либо вопросы, как рекомендует кнопка «понизить голос». Просто скопируйте/вставьте ошибку, и вы обнаружите множество обманов и обходных путей. Распаковка пакетов параметров в псевдонимах шаблона и Расширение пакета для шаблона псевдонимов
@user12002570 user12002570, спасибо за предоставление ссылок на потенциальные дубликаты. Прежде чем написать свой вопрос, я поискал и увидел эти вопросы и многое другое. Я не смог соединить их вместе, чтобы решить эту проблему. Я отредактировал свое сообщение и упомянул ссылки, которые вы предоставили, с объяснением того, почему я не смог решить эту проблему, используя их. Я думаю, что этот вопрос и данный ответ могут помочь другим, кто столкнулся с аналогичной конкретной ситуацией.
@user12002570 user12002570 Внимательно прочитав связанные дубликаты, я могу подтвердить, что предложенные в них обходные пути не подходят для этого ОП. Я не буду использовать свой дафаммер, чтобы опровергнуть этот вопрос, так как чувствую, что могу быть предвзятым (как автор единственного ответа). Но, пожалуйста, подумайте об этом. Голосование за возобновление работы. (также спасибо за CWG).
@YSC Поскольку фундаментальные проблемы те же, и ваше решение уже решило конкретную проблему OP, лучше оставаться рядом и связываться с обманщиками. Пожалуйста.
Кто из них прав [...]?
Ни один. Или оба.
При первом прочтении я подумал, что clang — это неправильно, поскольку: шаблон класса и шаблон псевдонима — синонимы1; оба они могут использоваться как аргументы шаблона для шаблона параметры шаблона взаимозаменяемо2.
Но комментаторы справедливо отметили, что это ничего не доказывает. После более внимательного прочтения Стандарта я пришел лишь к выводу, что норма не дает должного ответа на этот вопрос. GCC применяет то, чего ожидаем мы, разработчики, в то время как clang придерживается более консервативного прочтения правил.
Это, как указал пользователь 1200257, CWG 1430:
Первоначально расширение пакета не могло расширяться до списка параметров шаблона фиксированной длины, но это было изменено в N2555. Это отлично работает для большинства шаблонов, но вызывает проблемы с шаблонами псевдонимов.
[Есть ли] какое-нибудь обходное решение?
Следуя пословице:
Любую проблему можно решить, добавив уровень косвенности... за исключением слишком большого количества уровней косвенности.
#include <functional>
template<template <class...> class T, class... U>
struct pack
{ using type = T<U...>; };
template <template <typename...> class T>
struct s
{
template <typename... U>
using type = pack<T, U...>::type;
};
template <typename T>
class c {};
template <typename T>
using a = c<T>;
int main()
{
static_assert(std::is_same_v<c<int>, s<c>::type<int>>, "template-class");
static_assert(std::is_same_v<c<int>, s<a>::type<int>>, "template-alias");
}
=> Демо в Compiler Explorer <=
Имя, объявленное с помощью спецификатора typedef, становится именем typedef. Имя typedef называет тип, связанный с идентификатором ([dcl.decl]) или простым идентификатором шаблона ([temp.pre]); Таким образом, имя typedef является синонимом другого типа. Typedef-name не вводит новый тип, как это делает объявление класса ([class.name]) или объявление перечисления ([dcl.enum]).
Аргументом шаблона для параметра шаблона шаблона должно быть имя шаблона класса или шаблона псевдонима, выраженное как выражение id. При сопоставлении аргумента шаблона с соответствующим параметром учитываются только основные шаблоны; частичные специализации не учитываются, даже если их списки параметров совпадают со списком параметров шаблона шаблона.
Похоже, clang рассматривает шаблон псевдонима как частичный. Если я изменю псевдоним шаблона на частичный, GCC выдаст аналогичную ошибку. c++ template <typename T, typename U> class c {}; template <typename U> using a = c<int, U>;
Я не думаю, что это так просто. Напоминает мне cplusplus.github.io/CWG/issues/1244.html (1) говорит об эквивалентности типов, а не об эквивалентности шаблонов, поэтому он не благословляет автоматически код OP.
@YSC, круто. Он работает даже с частичными псевдонимами шаблонов. Спасибо,
Это CWG 1430