Неоднозначные шаблоны перегрузки

16

У меня есть следующий шаблонный код

#include <vector>
#include <array>
#include <iostream>

template<typename T1>
void foo(std::vector<T1> bar) {
    std::cout << "GENERIC" << std::endl;
}

template<typename T1>
void foo(std::vector<std::vector<T1>> bar) {
    std::cout << "SPECIFIC (vector)" << std::endl;
}

template<typename T1, int SIZE>
void foo(std::vector<std::array<T1, SIZE>> bar) {
    std::cout << "SPECIFIC (array)" << std::endl;
}

int main() {
    std::vector<std::vector<int>> a(2, std::vector<int> { 1, 2, 3});
    std::vector<std::array<int, 3>> b(2, std::array<int, 3> {4, 5, 6});

    foo(a);
    foo(b);
}

который производит

SPECIFIC (vector)
GENERIC

Мне интересно, почему версия вектора-вектора вызывается с конкретным шаблоном, а версия вектора-массива вызывается с универсальным?

Xaser
источник
2
К вашему сведению: вы можете упростить это, с той же проблемой, удалив внешнее vectorна всех из них. Смотрите здесь
ChrisMM
@ChrisMM хороший улов. Этот пример был синтезирован из моего производственного кода, где необходима вложенная структура.
Xaser
5
MSVC называет версию вектора массивов: godbolt.org/z/7Gfeb0
R2RT

Ответы:

8
template<typename T1, size_t SIZE>
void foo(std::vector<std::array<T1, SIZE>> bar) {
    std::cout << "SPECIFIC (array)" << std::endl;
}

Вы должны использовать std::size_tвместо int. беги сюда

Изменить: На самом деле, ваши комментарии и моя интуиция о коде заставили меня копаться в теме. На первый взгляд, стандартные разработчики (как и я) ожидают, что компилятор преобразует intих std::size_t(потому что они оба являются целочисленным типом, а неявное преобразование очень тривиально) и выбирает void foo(std::vector<std::array<T1, SIZE>> bar)наилучшую специализацию. Так что, читая страницу вывода аргументов шаблона, я нашел это:

Если в списке параметров используется нетипичный параметр шаблона, и выводится соответствующий аргумент шаблона, тип выведенного аргумента шаблона (как указано в прилагаемом списке параметров шаблона, то есть ссылки сохраняются) должен соответствовать типу нетипичный параметр шаблона, за исключением того, что cv-квалификаторы отбрасываются, и кроме случаев, когда аргумент шаблона выводится из границы массива - в этом случае допускается любой целочисленный тип, даже bool, хотя он всегда становится истинным:

Как всегда, конечно, вы должны прочитать несколько раз, чем один раз, чтобы понять, что это значит :)

Получается интересный результат.

Наша желаемая специализация уже не выбрана, но если бы компилятор был вынужден выбирать, это было бы ошибкой.

template<typename T1, int SIZE>
void foo(std::vector<std::array<T1, SIZE>> bar) {
    std::cout << "SPECIFIC (array)" << std::endl;
}

int main() {
    std::vector<std::array<int, 3>> b(2, std::array<int, 3> {4, 5, 6});

    foo(b); // P = std::vector<std::array<int,(int)SIZE>
            // A = std::vector<std::array<int,(unsigned_long)SIZE>>
            // error: deduced non-type template argument does not have the same
            // type as its corresponding template argument */
}

запустить код

Еще одна интересная вещь:

Если бы не типовой аргумент шаблона не был выведен, не было бы ограничения, которое заставляет типы аргумента и шаблона быть одинаковыми.

#include <vector>
#include <array>
#include <iostream>

template<typename T1, int SIZE>
void foo(std::vector<std::array<T1, SIZE>> bar) {
    std::cout << "SPECIFIC (array)" << std::endl;
}

int main() {
    std::vector<std::array<int, 3>> b(2, std::array<int, 3> {4, 5, 6});

    foo<int,3>(b);
}

запустить код

Arnes
источник
@ Xaser, потому что второй аргумент шаблона массива имеет тип size_t...
Жан-Батист Юнес,
2
Принимая во внимание комментарий R2RT, кажется, что есть определенные различия компилятора.
Xaser
8

Я думаю, что это просто из-за одной строки из[temp.deduct.call]/4

В общем, процесс дедукции пытается найти значения аргументов шаблона, которые сделают вывод A идентичным A

Чтобы уточнить, Aзначит параметр, из[temp.deduct.call]/1

... вывод аргумента шаблона с типом соответствующего аргумента вызова (назовите его A) ...

Как уже указывалось, переход template<typename T1, int SIZE>на template<typename T1, size_t SIZE>исправление проблемы, которую вы видите. Как указано в [temp.deduct.call]/4, компилятор ищет вывод, Aкоторый идентичен A. Так как an std::arrayимеет аргументы шаблона <class T, size_t N>(from [array.syn]), на самом деле его второй параметр - size_tнет int.

Следовательно, для вывода из шаблона ваша общая функция template<typename T1>способна точно соответствовать типу A, где ваш специалист template<typename T1, int SIZE>не является точным соответствием. Я считаю, что MSVC является неправильным в своем выводе.

ChrisMM
источник