Евгений Легоцкой26 февраля 2019 г. 5:22

Применение SFINAE для специализации шаблонных методов в зависимости от типа контейнера std::map или std::vector, передаваемых в качестве аргумента

Содержание

Встала задача: "написать вторую реализацию шаблонной функции", в которую передаются одни и те же аргументы. То есть сигнатура обеих функций полностью совпадает. Разница заключается в том, что в качестве аргумента в функцию могут передаваться как контейнеры std::vector, так и контейнеры std::map. И уже в зависимости от того std::vector это или std::map, должна выбираться та или иная реализация.

В данном случае будет использоваться правило SFINAE, которое гласит: Если не получается рассчитать окончательные типы аргументов (провести подстановку шаблонных параметров) перегруженной шаблонной функции, компилятор не выбрасывает ошибку, а ищет другую подходящую перегрузку. Ошибка будет в трёх случаях:

  • Не нашлось ни одной подходящей перегрузки.
  • Нашлось несколько таких перегрузок, и С++ не может решить, какую взять.
  • Перегрузка нашлась, она оказалась шаблонной, и при инстанцировании шаблона случилась ошибка.

Наш случай будет вторым. Имеется две перегрузки шаблонной функции.

Реализация

Для определения необоходимой перегрузки будет использоваться std::enable_if , который используется как раз для таких случаев, чтобы специализировать ту или иную функцию в зависимости от типа данных, который нужно обработать.

Стандартная библиотека STD, как оказалось не имеет функционала для определения, является ли контейнер std::map или нет. Но на StackOverflow уже обсуждался этот вопрос. Приведённый там код нужно лишь немного поправить, и правильно применить, чтобы написать специализацию функций в зависимости от типа контейнера.

Суть кода заключается в том, чтобы на этапе компиляции решить, является ли элемент контейнера std::pair и на основании этого решить, является ли контейнер std::map. Если контейнер является std::map, то выбираем одну функцию, в остальных случаях выбираем вторую реализацию функции.

#include <iterator>
#include <type_traits>

// шаблоны для определения является ли элемент контейнера std::pair
template <typename T>
struct is_pair : std::false_type {};

template <typename T, typename U>
struct is_pair<std::pair<T, U>> : std::true_type {};

template <typename T>
constexpr bool is_pair_v = is_pair<T>::value;

// шаблоны для определения является ли контейнера std::map
template<typename, typename = void>
struct is_mapping : std::false_type {};

template <typename Container>
struct is_mapping<Container, std::enable_if_t<is_pair_v<typename  std::iterator_traits<typename Container::iterator>::value_type>>> : std::true_type {};

template <typename T>
constexpr bool is_mapping_v = is_mapping<T>::value;

#include <map>
#include <vector>
#include <iostream>

class ClassWithSpecializedMethods
{
public:
    ClassWithSpecializedMethods() {}

    // Специализированный метод для обработки std::map
    template <class ContainerType>
    static typename std::enable_if<is_mapping_v<ContainerType>, void>::type printContainer(ContainerType& container);

    // Специализированный метод для обработки std::vector
    template <class ContainerType>
    static typename std::enable_if<!is_mapping_v<ContainerType>, void>::type printContainer(ContainerType& container);
};

// Реализации методов
template <class ContainerType>
inline
static typename std::enable_if<is_mapping_v<ContainerType>, void>::type ClassWithSpecializedMethods::printContainer(ContainerType& container)
{
    std::cout << "Map:" << std::endl;
    for (const auto& [key, value] : container)
    {
        std::cout << "Key: " << key << " Value: " << value << std::endl;
    }
    std::cout << std::endl;
}

template <class ContainerType>
inline
static typename std::enable_if<!is_mapping_v<ContainerType>, void>::type ClassWithSpecializedMethods::printContainer(ContainerType& container)
{
    std::cout << "Vector:" << std::endl;
    for (const auto& value : container)
    {
        std::cout << "Value: " << value << std::endl;
    }
    std::cout << std::endl;
}

// Тестируем работу функций
int main() {
    std::cout << "is_pair:" << std::endl;
    std::cout << "Map:    " << is_pair_v<std::iterator_traits<std::map<int, int>::iterator>::value_type> << std::endl;
    std::cout << "Vector: " << is_pair_v<std::iterator_traits<std::vector<int>::iterator>::value_type>   << std::endl;
    std::cout << std::endl;
    std::cout << "is_mapping:" << std::endl;
    std::cout << "Map:    " << is_mapping_v<std::map<int, int>> << std::endl;
    std::cout << "Vector: " << is_mapping_v<std::vector<int>>   << std::endl;
    std::cout << std::endl;

    std::map<int, int> map_container = {{1, 1}, {2, 2}, {3, 3}};
    std::vector<int> vector_container = {1, 2, 3};

    ClassWithSpecializedMethods::printContainer(map_container);
    ClassWithSpecializedMethods::printContainer(vector_container);
}

Используемые стандарты C++

Для работы данного кода необходимо использовать стандарт C++17, на меньшем стандарте код не скомпилируется.

В проектах на QMake в pro файл необходимо добавить следующие строки

CONFIG += console c++17
QMAKE_CXXFLAGS += /std:c++17

Заключение

В качестве заключения получаем следующий вывод

is_pair:
Map:    1
Vector: 0

is_mapping:
Map:    1
Vector: 0

Map:
Key: 1 Value: 1
Key: 2 Value: 2
Key: 3 Value: 3

Vector:
Value: 1
Value: 2
Value: 3

Как это выглядит в консоли

Вывод с использование SFINAE для std::map и std::vector

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.
Поддержать автора Donate

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
Timeweb

Позвольте мне порекомендовать вам отличный хостинг, на котором расположен EVILEG.

В течение многих лет Timeweb доказывает свою стабильность.

Для проектов на Django рекомендую VDS хостинг

Посмотреть Хостинг
ДК

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:10баллов,
  • Очки рейтинга-10
ДК

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:0баллов,
  • Очки рейтинга-10
RV

C++ - Тест 002. Константы

  • Результат:66баллов,
  • Очки рейтинга-1
Последние комментарии
s

Qt - Выбор данных из базы данных QSqlQuery в потоке QThread и создание на их основе модели QAbstractTableModel

Перенес в класс потока все функции для работы с БД, но все по старому когда закрываю поток основное соединение тоже закрывается
s

Qt - Выбор данных из базы данных QSqlQuery в потоке QThread и создание на их основе модели QAbstractTableModel

Упс, видимо нет, буду проверять. У меня просто собраны функции работы с базой данных, и подключение новое, но в функция #ifndef FUNCTIONS_H#define FUNCTIONS_H#include <QTextCodec>#i…

Qt - Выбор данных из базы данных QSqlQuery в потоке QThread и создание на их основе модели QAbstractTableModel

Вы полностью создаете новое соединение? И при создании объекта QSqlQuery или модели указываете алиас подключения?
s

Qt - Выбор данных из базы данных QSqlQuery в потоке QThread и создание на их основе модели QAbstractTableModel

Если в: QSQLDatabase db_thread = QSQLDatabase::addDatabase("MYSQL","db_new_name"); крашится после запуска сразу

Qt - Выбор данных из базы данных QSqlQuery в потоке QThread и создание на их основе модели QAbstractTableModel

В потоке надо создавать свое соединение с БД с другим именем.
Сейчас обсуждают на форуме
ДК

Qml, tableview

через делегат https://evileg.com/ru/post/624/

Связь таблиц Qt SQL

добрый, у вас по сути два варианта: 1. делать каскадное удаление из БД по внешним ключам(но если архетиктура базы с ошибками то могут быть сбои) 2. делать контролируемо удаление из вто…
A.

Работа с WinAPI в QT(изменение title bar)

void MainWindow::mousePressEvent(QMouseEvent *event){ if(event->pos().y() <= 45 && event->pos().y() >= 16) mpos = event->pos(); else if(event->pos().y…

QSqlTableModel - Как добавить картинки в таблицу, чтобы они отражались в диалоговом окне, но не были частью модели

Ну тогда в этом столбце указывайте пути на несколько картинок
U

Qt наследование и виджеты

Наверное нужно написать класс-интерфейс IBasic и потом попробовать IBasic *b = qobject_cast (tmpWidget); ? Но у меня в базовых классах A,B - формы... Получается, ч…
О нас
Услуги
© EVILEG 2015-2020
Рекомендует хостинг TIMEWEB