C++20 has a new feature called Concept that allows developers to specify constraints on template parameters. Template meta programming is a complex topic that stumps even seasoned veterans. I found an interesting usage of this feature to ensure that I can write an overloaded stream insertion operator that can output C++ containers that support range based iteration (basically types that have begin and end methods).
#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;
}
Running this program produces this output:
~/code/cpp/concepts > ./concept
[ 1 2 3 ]
[ 4 5 6 ]
[ 11->12 10->11 8->9 ]
So, we have a single overload that can support any container that has a begin and end method. This is not magic, you could have got this to work without SupportsRangeBasedFor
concept, but the magic is what the concept prevents from happening:
Without the constraint on the template parameter, compiler would have happily chosen operator<<
overload for Foo
and you would get an indecipherable blob of errors caused by the fact that Foo
doesn't have begin
and end
methods. You will see errors of this nature:
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;
Not only that, the compiler would match any ostream.operator<<(...)
call to this overload because the template parameter type (introduced by auto
type specifier) will match all types.
The type constraint expressed by the concept explicitly forbids the compiler from picking up your template function for types that you don't wish to support. This is a pretty neat feature and I hope to be using this quite widely in my future development to limit the types that my implementation should be chosen for :)
Top comments (0)