Разрешение имени для рекурсивного конечного типа возврата
Я обнаружил странную разницу между явным и автоматическим типом возвращаемого результата.
В следующем коде мы определяем структуру, созданную на основе целого числа и функции iter, которые принимают один объект этого типа в качестве аргумента. Тип возвращаемого значения зависит от результата вызова самого себя после уменьшения значения шаблона.
Чтобы разорвать цикл создания экземпляров (или так я думал), я предоставляю специализацию, которая возвращает независимый тип.
У нас есть главная игрушка для создания шаблонов.
Вот немного кода:
template<int i> struct Int {};
constexpr auto iter(Int<0>) -> Int<0>;
template<int i> constexpr auto iter(Int<i>) -> decltype(iter(Int<i-1>{}));
int main(){
decltype(iter(Int<10>{})) a;
}
Этот код не работает ни в gcc 4.9, ни в clang 3.5. Оба запускают бесконечную реализацию (они не соответствуют специализированному базовому случаю).
rec.cpp:11:62: fatal error: recursive template instantiation exceeded maximum depth of 256
template<int i> constexpr auto iter(Int<i>) -> decltype(iter(Int<i-1>{}));
Теперь, если мы используем C++14 decltype(auto)
и мы предоставляем тело для шаблона, которое возвращает ту же самую вещь:
template<int i> struct Int {};
constexpr auto iter(Int<0>) -> Int<0>;
template<int i>
constexpr auto iter(Int<i>) -> decltype(auto) {
return iter(Int<i-1>{});
}
int main(){
decltype(iter(Int<10>{})) a;
}
Теперь это работает для обоих компиляторов и ведет себя как ожидалось.
Я пробовал разные способы выразить специализацию и немного передвинул ее (чтобы быть осторожнее с ее местоположением), но это не помешало ее самосожжению;(
Я также пытался посыпать код более decltype
а также declval
, но я не могу заставить работать синтаксис C++11.
Может ли кто-нибудь объяснить разницу между двумя синтаксисами для поиска имени?
1 ответ
Это связано с относительным порядком разрешения перегрузки, разрешения перегрузки шаблона, создания объявления шаблона и создания определения шаблона.
Давайте сначала посмотрим на случай C++11. Когда компилятор должен оценить decltype(iter(Int<0>{}))
, он выполняет разрешение перегрузки на имя iter
называется с аргументами prvalue Int<0>
, Поскольку шаблон находится в наборе перегрузки, мы применяем 14.8.3 [temp.over]:
1 - Шаблон функции может быть перегружен либо (не шаблонными) функциями своего имени, либо (другими) шаблонами функций с тем же именем. Когда написано обращение к этому имени (явно или неявно с использованием нотации оператора), для каждого шаблона функции производится поиск значений аргумента шаблона (14.8.2) и проверка любых явных аргументов шаблона (14.3) (если таковые имеются), которые могут использоваться с этим шаблоном функции для создания экземпляра специализации шаблона функции, которая может быть вызвана с аргументами вызова. [...]
В результате декларация template<int i> constexpr auto iter(...) -> ...
создается (14.7.1p10 [temp.inst]) с i = 0
, что заставляет оценку decltype(iter(Int<-1>{}))
и вниз по кроличьей норе отрицательных целых чисел мы идем.
Это не важно constexpr auto iter(Int<0>) -> Int<0>
было бы лучше перегрузить (на 13.3.3p1 [over.match.best]), потому что мы никогда не заходим так далеко; компилятор весело идет в отрицательную бесконечность.
В отличие от этого, с C++14 выведенных типов возврата 7.1.6.4p12 [dcl.spec.auto] применяется:
12 - Вывод возвращаемого типа для шаблона функции с заполнителем в объявленном типе происходит, когда создается определение [...]
Поскольку определение определения происходит после разрешения перегрузки шаблона (14.7.1p3), плохой шаблон iter<0>
никогда не создается; 14.8.3p5:
5 - Для ввода специализации в набор функций-кандидатов необходима только подпись специализации шаблона функции. Поэтому для разрешения вызова, для которого является специализацией шаблона, требуется только объявление шаблона функции.
"Подпись" iter<0>
здесь (Int<0>) -> decltype(auto)
подпись, содержащая тип заполнителя (7.1.6.4).
Предлагаемый обходной путь: используйте SFINAE для предотвращения любых попыток вызова iter(Int<-1>{})
:
template<int i> constexpr auto iter(Int<i>)
-> decltype(iter(typename std::enable_if<i != 0, Int<i-1>>::type{}));
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^
Обратите внимание, что СФИНА должен идти внутрь decltype
и действительно внутри звонка iter
,