Вопрос или проблема
Существует очень похожий вопрос, но я не понял, как я мог бы применить обходные решения к своему коду. Дело больше в достижении универсальности для различных типов контейнеров: Visual C++ не может вывести шаблонный параметр шаблона
Чтобы повторить: MSVC, похоже, не может вывести аргумент шаблона-шаблона для шаблонной функции в некоторых случаях из-за неконформного поведения. Clang и GCC компилируются нормально. Есть ли способ заставить код компилироваться, не указывая явно аргументы шаблона при каждом вызове шаблонной функции?
https://godbolt.org/z/PYe3aoYaM
Обратите внимание: я не думаю, что вариативный <class...>
должен быть обязательным, но он решил упрощенную задачу (также здесь Clang и GCC приняли это без ...
)
#include <vector>
#include <memory>
// Универсальный тип вектора, который должен быть взаимозаменяемым.
template <typename T>
using MyVec = std::vector<T>; // добавление std::allocator<T> не помогает
// Тип для определения вектора владящих ссылок.
template <typename T, template <class...> typename C>
using Group = C<std::unique_ptr<T>>;
// Тип для определения вектора невладящих ссылок.
template <typename T, template <class...> typename C>
using GroupRef = C<T*>;
// Преобразование владящих ссылок в невладящие.
template <typename T, template <class...> typename C>
GroupRef<T, C> toRef(const Group<T, C>& arg)
{
GroupRef<T, C> result;
for (const auto& p : arg) {
result.push_back(p.get());
}
return result;
}
int main() {
Group<int, MyVec> g;
// Ошибка: "не найдена подходящая перегруженная функция"
auto x = toRef(g);
// Это работает:
//auto x = toRef<int, std::vector>(g);
}
Ответ или решение
Как обойти проблемы с дедукцией аргумента шаблона в MSVC
Введение
При работе с шаблонами в C++ вы можете столкнуться с трудностями, когда компилятор не может автоматически вывести аргументы шаблона. Это особенно актуально для компилятора Microsoft Visual C++ (MSVC), который иногда проявляет поведение, не соответствующее стандартам. В данной статье мы проанализируем проблему и предложим способы ее обхода, рассматривая представленный выше код.
Проблема
Вы пытались использовать функцию toRef
, которая принимает аргумент типа Group
. Однако MSVC не может вывести typename C
, что приводит к ошибке компиляции. В отличие от MSVC, компиляторы Clang и GCC успешно компилируют этот код без необходимости явного указания шаблонных параметров.
Почему это происходит?
Ошибка возникает, потому что MSVC не может корректно вывести шаблонный аргумент template<typename...> typename C
в контексте передачи аргумента типа Group
. Ваша подача кодов наилучшим образом демонстрирует, что использование вариативных шаблонов (class...
) хотя и является решением, не является строго необходимым в большинстве случаев.
Решения
1. Явное указание аргументов шаблона
Первый и наиболее прямой подход — это явное указание аргументов шаблона при вызове функции. Хотя это и увеличивает объем кода, это наиболее стабильное решение:
auto x = toRef<int, std::vector>(g);
Это позволит избежать ошибки и сделать код совместимым с MSVC.
2. Использование вспомогательных шаблонов для обертки
Второй способ — это создание вспомогательного шаблона для автоматического определения типов. Например, можно создать вспомогательную структуру, которая будет извлекать типы из контейнеров:
template <typename T>
struct ExtractContainer {
using type = std::vector<std::unique_ptr<T>>;
};
template <typename T>
using DefaultContainer = typename ExtractContainer<T>::type;
// Обновленный вызов
auto x = toRef<int, DefaultContainer>(g);
С помощью этого подхода вы можете скрыть детали определения контейнера и сделать ваш код чище.
3. Использование вариативных шаблонов
Если все же необходимо сохранить унифицированный подход для любого контейнера, можно использовать вариативные шаблоны в качестве универсального решения, принимающего разные контейнеры. Пример:
template <typename T, template <typename, typename...> class C, typename... Args>
GroupRef<T, C<T, Args...>> toRef(const Group<T, C<T, Args...>>& arg)
{
GroupRef<T, C<T, Args...>> result;
for (const auto& p : arg) {
result.push_back(p.get());
}
return result;
}
Заключение
Работа с шаблонами в C++ требует тщательного обращения, особенно с учетом особенностей компиляторов. В данной статье мы рассмотрели, как обойти проблемы с дедукцией аргументов шаблона в MSVC, предложив практичные решения, включая явное указание аргументов, создание вспомогательных шаблонов и использование вариативных шаблонов.
Эти подходы позволят вам упрощать код и избегать проблем с совместимостью в разных компиляторах, сохраняя при этом гибкость и чистоту вашей программы.