Вложение вложенного аргумента шаблона для шаблонов классов не работает
В этих вопросах и ответах я написал небольшой класс-обертку, который предоставляет обратный итератор для доступа к диапазону, полагаясь на вывод аргумента шаблона функции языка C++1z для шаблонов классов ( p0091r3, p0512r0)
#include <iostream>
#include <iterator>
#include <vector>
template<class Rng>
class Reverse
{
Rng const& rng;
public:
Reverse(Rng const& r) noexcept
:
rng(r)
{}
auto begin() const noexcept { using std::end; return std::make_reverse_iterator(end(rng)); }
auto end() const noexcept { using std::begin; return std::make_reverse_iterator(begin(rng)); }
};
int main()
{
std::vector<int> my_stack;
my_stack.push_back(1);
my_stack.push_back(2);
my_stack.puhs_back(3);
// prints 3,2,1
for (auto const& elem : Reverse(my_stack)) {
std::cout << elem << ',';
}
}
Тем не менее, делая вложенное приложение Reverse
не дает исходный порядок итераций
// still prints 3,2,1 instead of 1,2,3
for (auto const& elem : Reverse(Reverse(my_stack))) {
std::cout << elem << ',';
}
Живой пример (один и тот же вывод для g++ 7.0 SVN и clang 5.0 SVN)
Похоже, виновником является вывод аргумента шаблона для шаблонов классов, потому что обычная функция-обертка допускает правильное вложение
template<class Rng>
auto MakeReverse(Rng const& rng) { return Reverse<Rng>(rng); }
// prints 1,2,3
for (auto const& elem : MakeReverse(MakeReverse(my_stack))) {
std::cout << elem << ',';
}
Живой пример (тот же вывод для g ++ и clang)
Вопрос: вывод вложенных аргументов шаблона для шаблонов классов должен работать только на уровне "один уровень", или это ошибка в текущих реализациях g ++ и clang?
2 ответа
Ответ Петра правильно объясняет, что происходит - конструктор перемещения лучше соответствует вашему шаблону конструктора.
Но (как обычно h / t TC) есть лучшее решение, чем просто написать фабрику: вы можете добавить явное руководство по выводу для своего конструктора:
template <class R>
Reverse(R ) -> Reverse<R>;
Это кажется излишним с вашим уже существующим конструктором, так как оно определяет точно такое же правило. Но в [over.match.best] есть новое предпочтение:
Учитывая эти определения, жизнеспособная функция
F1
определяется как лучшая функция, чем другая жизнеспособная функцияF2
если [...]F1
генерируется из руководства по удержанию (13.3.1.8) иF2
не является.
Следовательно, у нас было бы четыре сгенерированные функции, снова заимствуя из имен Петра:
template <typename Rng>
Reverse<Rng> foo(const Rng& r); // #1
template <typename Rng>
Reverse<Rng> foo(const Reverse<Rng>& r); // #2
template <typename Rng>
Reverse<Rng> foo(Reverse<Rng>&& r); // #3
template <typename Rng>
Reverse<Rng> foo(Rng r); // #4 - same-ish as #1, but deduction guide
До, #3
был предпочтен как более специализированный. Сейчас, #4
является предпочтительным как руководство по удержанию. Итак, мы все еще можем написать:
for (auto const& elem : Reverse(Reverse(my_stack))) {
std::cout << elem << ',';
}
и это работает. Во всяком случае, на лязг.
Это может быть объяснено в http://eel.is/c++draft/over.match.class.deduct:
Набор функций и шаблонов функций состоит из:
- Для каждого конструктора шаблона класса, обозначенного именем шаблона, существует шаблон функции со следующими свойствами:
Параметры шаблона - это параметры шаблона класса, за которыми следуют параметры шаблона (включая аргументы шаблона по умолчанию) конструктора, если таковые имеются.
Типы параметров функции - это типы конструктора.
Возвращаемый тип - это специализация шаблона класса, обозначаемая аргументами имени шаблона и шаблона, соответствующими параметрам шаблона, полученным из шаблона класса.
Насколько я понимаю, компилятор изобретает следующие две функции (две - включая конструктор копирования, который неявно генерируется для этого класса):
template <typename Rng>
Reverse<Rng> foo(const Rng& r); // #1
template <typename Rng>
Reverse<Rng> foo(const Reverse<Rng>& r); // #2
и затем пытается выбрать лучшую перегрузку на основе вызова:
foo(Reverse<std::vector<int>>(my_stack));
который разрешается до #2, потому что этот является более специализированным. Вывод таков:
Reverse(Reverse(my_stack))
включает в себя конструктор копирования для создания внешнего Reverse
пример.