Концепции C++ в действии

В C++20 появилась новая возможность под названием Concept, которая позволяет разработчикам задавать ограничения на параметры шаблонов. Метапрограммирование шаблонов — сложная тема, которая ставит в тупик даже опытных ветеранов. Я нашел интересное применение этой функции, чтобы убедиться, что я могу написать перегруженный оператор вставки потока, который может выводить контейнеры C++, поддерживающие итерацию на основе диапазона (в основном типы, имеющие методы begin и end).

#include <iostream>
#include <unordered_map>
#include <array>
#include <vector>
using namespace std;

template<typename ContainerType>
concept SupportsRangeBasedFor = requires(ContainerType container) {
    container.begin();
    container.end();
};

template<typename KeyType, typename ValueType>
ostream& operator<<(ostream& os, const pair<KeyType, ValueType>& p) {
    os << p.first << "->" << p.second;
    return os;
}

ostream& operator<<(ostream& os, SupportsRangeBasedFor auto&& container) {
    os << "[ ";
    for (const auto& element : container) {
        os << element << ' ';
    }
    os << ']' << endl;
    return os;
}

int main()
{
    cout << vector<int>{1,2,3} << endl;
    cout << array<int,3>{4,5,6} << endl;
    cout << unordered_map<int,int>{{8,9},{10,11},{11,12}} << endl;
    //struct Foo {};
    //cout << Foo {} << endl; // Compiler Error: No overload for operator<< that accepts Foo
    return 0;
}
Вход в полноэкранный режим Выход из полноэкранного режима

Запуск этой программы приводит к такому результату:

~/code/cpp/concepts > ./concept
[ 1 2 3 ]

[ 4 5 6 ]

[ 11->12 10->11 8->9 ]

Вход в полноэкранный режим Выход из полноэкранного режима

Итак, у нас есть единственная перегрузка, которая может поддерживать любой контейнер, имеющий метод begin и end. Это не волшебство, вы могли бы заставить это работать и без концепции SupportsRangeBasedFor, но волшебство заключается в том, что эта концепция предотвращает:

Без ограничения на параметр шаблона компилятор с радостью выбрал бы перегрузку operator<< для Foo, и вы получили бы неразборчивую кучу ошибок, вызванных тем, что Foo не имеет методов begin и end. Вы увидите ошибки такого рода:

concept.cpp:22:30: error: invalid range expression of type 'Foo'; no viable 'begin' function available
    for (const auto& element : container) {
                             ^ ~~~~~~~~~
concept.cpp:35:10: note: in instantiation of function template specialization 'operator<<<Foo>' requested here
    cout << Foo {} << endl; 
Войдите в полноэкранный режим Выход из полноэкранного режима

Мало того, компилятор будет соответствовать любому вызову ostream.operator<<(...) этой перегрузки, потому что тип параметра шаблона (введенный спецификатором типа auto) будет соответствовать всем типам.

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

Оцените статью
Procodings.ru
Добавить комментарий