检测类型是否为“映射”

5
我想使用c++容器的::iterator成员类型将它们解析到另一个对象中。迭代器成员类型指向单一类型对象的容器(如向量、队列等)将转换为类似列表的对象,而迭代器成员类型指向std::pair的容器将转换为类似于映射的对象。
我正在尝试编写一个成员函数来检测后面一种类型的容器,但它不能正常工作。以下是我的代码:
#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;
}

由于某些原因,is_mapping_v 总是为 false,代码的输出结果如下:

$ g++ -std=c++14 temp.cc && ./a.out
is_pair:
Map:    1
Vector: 0

is_mapping:    
Map:    0
Vector: 0

我的代码哪里出问题了?


注意:这不是检查类型是否为map的重复问题。那个问题的答案使用::key_type::mapped_type成员来检测map(但对于像std::multimap这样的类失败)。我还明确使用迭代器指向后面的std::pair,因此检查它更有意义。


可能是检查类型是否为映射的重复问题。 - Daniel
2个回答

8
is_pair_v<std::iterator_traits<typename Container::iterator>::value_type>

应该是

is_pair_v<typename std::iterator_traits<typename Container::iterator>::value_type>

因为value_type是一种类型。没有typename,它将被解析为值并失败,enable_if因此回退到主模板。

你在main中的测试之所以产生正确的值,是因为那里的模板已经被实例化,而且value_type是一种类型而非值,不存在歧义。

第二个错误在于你的主模板。

template<typename...>

应该是

template<typename, typename = void>

否则,is_mapping<T>永远不会成为具有两个参数的特化版本,因为参数计数不匹配。 现场

即使有了那个修复,这仍然无法工作:http://coliru.stacked-crooked.com/a/2ab49251e70c35aa - Justin
@Justin 你说得对,我忘了提到我已经修复了主模板,糟糕。 - Passer By

4
编译器没有选择部分特化结构。原因有两个:
  1. 您对 is_pair_v<std::iterator_traits<typename Container::iterator>::value_type> 执行了 enable_if 操作。编译器会将 std::iterator_traits<...>::value_type 视为值而不是类型,但 SFINAE 会生效并将其静默删除。
  2. 使用 template <typename...> 使得不能选择部分特化。如果我没错的话,这是因为 is_mapping<T> 可以通过初始定义解析为 is_mapping<T>,也可以通过特化解析为 is_mapping<T, void>。在这种情况下,似乎优先选择 is_mapping<T>

    修复方法很简单,只需将其更改为 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 { };

然而,一个更简单的 is_mapping 实现如下:
template <typename Container>
struct is_mapping : is_pair<
    typename std::iterator_traits<typename Container::iterator>::value_type
> {};

基本上,你的版本看起来像这样。
if (is_pair(...)) return true;
else return false;

相比较而言,您可以简单地执行以下操作:

return is_pair(...)

简单的实现对于没有 ::iterator 成员类型的类型无法工作。 - Ron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接