Разрешение имени для рекурсивного конечного типа возврата

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

В следующем коде мы определяем структуру, созданную на основе целого числа и функции 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,

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