Является ли это "если e является пакетом, то получите имя шаблона, иначе получите имя переменной" valid "или нет?
Я попытался построить случай, который не требует имени или шаблона, но все же дает переменную или шаблон в зависимости от того, является ли данное имя t
пакетом параметров функции или нет
template<typename T> struct A { template<int> static void f(int) { } };
template<typename...T> struct A<void(T...,...)> { static const int f = 0; };
template<typename> using type = int;
template<typename T> void f(T t) { A<void(type<decltype(t)>...)>::f<0>(1); }
int main() {
f(1);
}
Вышеуказанное относится к static const int
и выполняет сравнение. Только что T t
был изменен как пакет и сделать f
ссылкой на шаблон, но GCC не нравится ни
template<typename ...T> void f(T ...t) { A<void(type<decltype(t)>...)>::f<0>(1); }
int main() {
f(1, 2, 3);
}
GCC жалуется на первый
main.cpp:5:68: error: incomplete type 'A<void(type<decltype (t)>, ...)>' used in nested name specifier
template<typename T> void f(T t) { A<void(type<decltype(t)>...)>::f<0>(1); }
И для второго
main.cpp:5:74: error: invalid operands of types '<unresolved overloaded function type>' and 'int' to binary 'operator<'
template<typename ...T> void f(T ...t) { A<void(type<decltype(t)>...)>::f<0>(1); }
У меня есть несколько вопросов
- Работает ли указанный выше код в соответствии с языком или есть ошибка?
- Поскольку Clang принимает оба варианта, но GCC отклоняет, я хотел спросить, какой компилятор прав?
-
Если я удалю тело первичного шаблона, то для случая f(1, 2, 3)
Clang жалуется
main.cpp:5:42: error: implicit instantiation of undefined template 'A<void (int)>'
Обратите внимание, что он говорит A<void (int) >
, в то время как я ожидал A<void (int, int, int)>
. Как это происходит? Является ли это ошибкой в моем коде - то есть она неформальная, или это ошибка в Clang? Кажется, я помню отчет о дефекте о порядке расширения и подстановке шаблона псевдонима, является ли это актуальным и делает ли мой код плохо сформированным?
Ответы
Ответ 1
Расширение пакета параметров либо должно, либо делает тип зависимости зависимым. Независимо от того, расширяются ли вещи, зависит от типа.
Если бы этого не произошло, в правилах зависимостей типа С++ появлялась бы явная дыра, и это было бы дефектом в стандарте.
So A<void(type<decltype(t)>...)>::f
, когда t
- это пакет, независимо от того, какие трюки вы втягиваете в void(
здесь )
для распаковки t
, должен быть зависимым типом, а template
требуется перед f
, если это template
.
В случае, когда t
не является пакетом, предполагается, что type<decltype(t)>
не зависит (см. http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1390), но стандарт может или не может согласиться на данный момент (я думаю, что нет?)
Если компиляторы сделали "то, что комитет намеревался", тогда, когда t
не является пакетом:
A<void(type<decltype(t)>...)>::f<0>(1)
может означать
A<void(int...)>::f<0>(1)
который
A<void(int, ...)>::f<0>(1)
и если f
является template
(ваш код делает это int
, но я думаю, что замена этих двух должна работать), это будет хорошо. Но стандарт, по-видимому, в настоящее время не согласен?
Итак, если http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1390, вы можете поменять свои две специализации A
. Специализация void(T...,...)
должна иметь template<int> void f(int)
, а специализация t
должна иметь static const int
.
Теперь в случае, когда A<>
зависит (от размера пакета), ::f
является int
и не нуждается в template
. В случае, когда A<>
не зависит, ::f
является template
, но не нуждается в значении.
Мы можем заменить type<decltype(t)>...
на:
decltype(sizeof(decltype(t)*))...
и sizeof(decltype(t)*)
имеет независимый тип (это std::size_t
), decltype
дает нам std::size_t
, а ...
рассматривается как арка старой школы ...
. Это означает, что void(std::size_t...)
становится независимым типом, поэтому A<void(std::size_t...)>
не зависит, поэтому ::f
является шаблоном не является шаблоном в зависимом контексте.
В случае, когда t
представляет собой пакет параметров с одним элементом
decltype(sizeof(decltype(t)*))...
становится
std::size_t
но в зависимом контексте (один экземпляр на элемент в пакете t
). Итак, мы получаем
A<void(std::size_t)>::f
который считается скалярным значением, поэтому
A<void(std::size_t)>::f<0>(1)
становится выражением, оценивающим false
.
(Цепь логики, сгенерированная в дискуссии с Йоханнесом в комментариях в оригинальном вопросе).
Ответ 2
Ваш второй случай плохо сформирован; A<void(type<decltype(t)>...)>::f<0>(1)
должен быть
A<void(type<decltype(t)>...)>::template f<0>(1)
// ~~~~~~~~~
В первом случае оба компилятора ведут себя некорректно; это считалось достаточно запутанным, что CWG 1520 был поднят для запроса правильного поведения; вывод заключался в том, что расширение пакета следует применять перед заменой псевдонимом:
Последняя интерпретация (список специализаций) - правильная интерпретация; пакет параметров не может быть заменен ничем, включая специализацию шаблона псевдонимов. CWG считает, что это достаточно ясно в текущей формулировке.
Это напоминает CWG 1558 (шаблоны псевдонимов и SFINAE), который был исправлен для С++ 14, но на вышеописанном уровне Ожидается, что компиляторы С++ 11 вернут это правильно, поэтому разочарование в том, что gcc и clang ошибаются (хотя, с полным основанием, они ведут себя корректно в более простых случаях, включая пример мотивации в CWG 1520). Обратите внимание, что до недавнего времени MSVC имел аналогичную ошибку; он зафиксирован в VS2015.
Ваш код (только в первом случае) правильный; но в качестве обходного пути вы можете изменить свой шаблон псевдонима для использования и отбросить его параметр шаблона, исправляя свою программу для обоих компиляторов - конечно, это означает, что ваш CWG 1390 эксплуатация перестанет быть действительной:
template<typename T> using type = decltype(((int(*)(T*))(0))(0)); // int
Однако я не думаю, что ваш трюк CWG 1390 может работать как представленный, поскольку, хотя подстановка подстановки type<decltype(t)>...
не зависит от типов t...
, она зависит от их числа:
template<typename T> struct A { template<int> static void f(int) {} };
template<> struct A<void(int, int, int)> { static const int f = 0; };
Как указывает Якк, его можно заставить работать, если вы поменяете шаблон функции-члена и элемент данных, поскольку член данных в порядке в зависимом контексте.