I'd like to parse c++ containers into another object using their ::iterator
member type. Containers who's iterator member type points to an object of a single type (vectors, queues, etc.) will turn into a list-like object, and containers who's iterator member type points to an std::pair
will turn into a map-like object.
I'm trying to write a member function to detect the latter kind of container, but it does not work. Here's what I have so far:
#include <tuple>
#include <iterator>
#include <type_traits>
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;
template <typename...>
struct is_mapping : std::false_type { };
template <typename Container>
struct is_mapping<Container, std::enable_if_t<
is_pair_v<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>
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;
}
For some reason, is_mapping_v
is always false and the code results in this output:
$ g++ -std=c++14 temp.cc && ./a.out
is_pair:
Map: 1
Vector: 0
is_mapping:
Map: 0
Vector: 0
What is wrong with my code?
Note: This is not the a duplicate of Checking if a type is a map. The answer to that question uses the ::key_type
and ::mapped_type
members to detect the map (It fails for classes such as std::multimap
). I am also explicitly using the fact that the iterator points to std::pair
s later in my code, so checking that makes a lot more sense.
is_pair_v<std::iterator_traits<typename Container::iterator>::value_type>
should be
is_pair_v<typename std::iterator_traits<typename Container::iterator>::value_type>
because value_type
is a type. Without the typename
, it will be parsed as a value and fail the enable_if
, thus falling back to the primary template.
The reason your tests in main
yield the correct value is because the templates there have already been instantiated and there is no ambiguity whether value_type
is a type or value.
The second error is your primary template
template<typename...>
should be
template<typename, typename = void>
Otherwise, is_mapping<T>
will never be the specialization with two arguments because the argument count mismatch.
Live
The compiler is not selecting the partially specialized struct. There are two reasons for this:
enable_if
off of is_pair_v<std::iterator_traits<typename Container::iterator>::value_type>
. The compiler would see std::iterator_traits<...>::value_type
as a value instead of a type, but SFINAE kicks in and this is just silently removed.Using template <typename...>
makes it so that the specialization isn't selected. If I am right, this is because is_mapping<T>
could either resolve to is_mapping<T>
using the initial definition, or is_mapping<T, void>
using the specialization. It seems that is_mapping<T>
is preferred in this case.
Fixing this is as easy as changing that to template <typename, typename = void>
:
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 { };
However, a simpler implementation of is_mapping
would be:
template <typename Container>
struct is_mapping : is_pair<
typename std::iterator_traits<typename Container::iterator>::value_type
> {};
Basically, your version looks like
if (is_pair(...)) return true;
else return false;
Whereas you could simply do
return is_pair(...)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With