Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect if type is a "mapping"

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::pairs later in my code, so checking that makes a lot more sense.

like image 938
Ron Avatar asked Dec 24 '22 16:12

Ron


2 Answers

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

like image 190
Passer By Avatar answered Jan 31 '23 12:01

Passer By


The compiler is not selecting the partially specialized struct. There are two reasons for this:

  1. You do an 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.
  2. 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(...)
like image 21
Justin Avatar answered Jan 31 '23 13:01

Justin