In short:
it is possible to write a C++20 function that accepts as argument a map, unordered_map or any other (present or future) implementation of a map ?
if I want restrict some parameter of a template to values with the behavior of a map (map, unordered_map or any other that appears), which C++20 concept must I use?
Long explanation:
When switching from one programming language to another, it is usual that you miss some features of the previous one in the next one. Usually that does means one language better than another (please, no answers nor comments to start a language war) , but is the result of design decisions in the languages and personal preferences of the coder.
In particular, when switching from Java to C++, I miss the strongly structured schema of classes of Java. By example, there are one interface Map that defines what is expected of a Map, and there are the different implementations (HashMap, TreeMap, ...) of it, optimized to some scenarios. In this way, the user can create code independent of the implementation (except when constructing the container). In C++, the are two usual map templates, "map" and "unordered_map":
template <typename _Key, typename _Tp, typename _Compare = std::less<_Key>,
typename _Alloc = std::allocator<std::pair<const _Key, _Tp> > >
class map
{ ...
template<typename _Key, typename _Tp,
typename _Hash = hash<_Key>,
typename _Pred = equal_to<_Key>,
typename _Alloc = allocator<std::pair<const _Key, _Tp>>>
class unordered_map
{ ...
as you can see, they do not share any common base class, thus, it is not possible (?) define an user function that accepts both kind of maps.
In addition, C++20 templates allows "concepts" to restrict the template parameters. How to write a concept equivalent to Java "<T implements Map<String,Integer>" ?. It seems necessary reimplement as concepts near than all class interfaces (?).
I assume that by saying:
I want restrict some parameter of a template to values with the behavior of a map
you mean that you want a template type parameter to be a map-like type. I can actually answer both parts of this question with one function prototype (see doMapStuff
below). First, you need to check if a given type is a "map
" or "map-like".
Normally, you need first some sort of interface to check if a member has specific common members. But because we're just checking if a type is "map-like", we can just check member types. In the case of map
-like types, a simple way would be to test the value_type
, key_type
, and mapped_type
member types.This can easily be done even without C++20
Starting with C++11, we can use std::is_same<T>
with a static assert like this:
template<class MapTy>
struct is_map_type : std::is_same<typename MapTy::value_type, std::pair<const typename MapTy::key_type, typename MapTy::mapped_type>> {};
Or, in C++17 and onwards you can even do this (using C++17's std::is_same_v
helper):
template<class MapTy>
constexpr bool is_map_type_v = std::is_same_v<typename MapTy::value_type, std::pair<const typename MapTy::key_type, typename MapTy::mapped_type>>
What these do is essentially check if the template type's value_type
(which exists for many different collection types) is exactly astd::pair
of the type's const key_type
and mapped_type
. This is true and thus will check for "any" map-like type, including std::multimap
and std::unordered_multimap
.
There are multiple ways to implement is_map_type
, even Pre-C++11, some of which can be seen in this StackOverflow question (my code snippet above was partially taken from that question).
However, if you specifically want to use C++20 concept
s, it's also very simple to do so if you want to implement it the same way:
template<class MapTy>
concept is_map_type = std::is_same_v<typename MapTy::value_type, std::pair<const typename MapTy::key_type, typename MapTy::mapped_type>>
Or if you want to use C++20's built-in std::same_as
concept:
template<class MapTy>
concept is_map_type = std::same_as<typename MapTy::value_type, std::pair<const typename MapTy::key_type, typename MapTy::mapped_type>>;
it is possible to write a C++20 function that accepts as argument a map, unsorted_map or any other (present or future) implementation of a map ?
if I want restrict some parameter of a template to values with the behavior of a map (map, unsorted_map or any other that appears), which C++20 concept must I use?
You can actually do both with one function, like I said, both with and without C++20.
In C++11 onward:
template <class MapTy>
void doMapStuff(const MapTy& m) { //function argument doesn't have to be const reference, I'm just doing that in the example
static_assert(is_map_type<MapTy>::value, "Passed map type isn't a valid map!");
//Or, if using the C++17 onward version:
static_assert(is_map_type_v<MapTy>, "Passed map type isn't a valid map!");
//Do something with argument 'm'...
}
Or, using C++20 requires
with the C++20 concept
we made:
template <class MapTy>
requires is_map_ty<MapTy>
void doMapStuff(const MapTy& m) {
//...
}
Basically, in either version the function will make sure that the passed template parameter will always satisfy the requirements we set for our map-like type (which in this case checks value_type
, key_type
, and mappped_type
like I said). Otherwise, the program won't compile; In the case of the static_assert
version, you'll get the message string and a compilation error during compiling. With the C++20 concept
version, you'' get a compilation error saying that the arguments passed to the doMapStuff
are of incorrect type.
Since the template type is used as the type of the first argument, you can use the function like this (without having to explicitly specify the type separately):
std::map<std::string, int> myMap;
doMapStuff(myMap);
//...
std::vector<int> myVec;
doMapStuff(myVec); //compile error! MapTy = std::vector<int>
It seems based on what you have said in the comments that you only want to check if value_type
is any pair, which a bit different from before. We first need a type trait to check if a template type is a pair. This is pretty much the same regardless of the C++ version:
template <typename>
struct is_pair : std::false_type { };
template <typename K, typename V>
struct is_pair<std::pair<K, V>> : std::true_type { };
This will create a bool_cosntant
struct we can use to check if a passed type is a valid pair. We then can define our is_map
type trait similarly to before.
In C++11 onward:
template<class MapTy>
struct is_map : is_pair<typename MapTy::value_type> {};
//C++17 compliant option
template<class MapTy>
constexpr bool is_map_v = is_pair<typename MapTy::value_type>::value;
In C++20 using concept
s:
template<class MapTy>
concept is_map_like = is_pair<typename MapTy::value_type>::value;
The usage in functions is pretty much the same; we once again don't have to explicitly pass a template type - it's deduced from the passed argument.
In C++11:
template <class MapTy>
void doMapStuff(const MapTy& m) {
static_assert(is_map_v<MapTy>, "Passed map type doesn't have a valid pair!");
//OR...
static_assert(is_map<MapTy>::value, "Passed map type doesn't have a valid pair!");
}
In C++20 using concepts
:
template <class MapTy>
requires is_map_like<MapTy>
void doMapStuff(const MapTy& m) {
//...
}
Although, it is important to note that the mapped_type
alias is unique to std "map-like"s, unlike value_type
; The original is_map_type
checks both. See this SO question for more details.