Detect if type is a "mapping"
Asked Answered
B

2

5

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.

Bannon answered 11/7, 2017 at 18:37 Comment(1)
Possible duplicate of Checking if a type is a mapAtchley
G
8
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

Gaynell answered 11/7, 2017 at 19:3 Comment(2)
Even with that fix, this still doesn't work: coliru.stacked-crooked.com/a/2ab49251e70c35aaRoutinize
@Routinize You're right, forgot to mention I fixed the primary template, oopsGaynell
R
4

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(...)
Routinize answered 11/7, 2017 at 18:56 Comment(1)
The simpler implementation does not work for types which do not have an ::iterator member type.Bannon

© 2022 - 2024 — McMap. All rights reserved.