В 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
) будет соответствовать всем типам.
Ограничение типа, выраженное этим понятием, явно запрещает компилятору подхватывать вашу шаблонную функцию для типов, которые вы не хотите поддерживать. Это довольно изящная возможность, и я надеюсь использовать ее довольно широко в своих будущих разработках для ограничения типов, для которых должна быть выбрана моя реализация 🙂