Как обойти проблему с тем, что MSVC не выводит аргумент шаблона шаблона

Вопрос или проблема

Существует очень похожий вопрос, но я не понял, как я мог бы применить обходные решения к своему коду. Дело больше в достижении универсальности для различных типов контейнеров: 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, предложив практичные решения, включая явное указание аргументов, создание вспомогательных шаблонов и использование вариативных шаблонов.

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

Оцените материал
Добавить комментарий

Капча загружается...