【问题标题】:using correct container type depending on a template being hashable根据可散列的模板使用正确的容器类型
【发布时间】:2018-02-08 23:10:11
【问题描述】:

我正在开发一个需要关联容器的 C++ 类(mapunordered_map

我的班级是这样的:

    template<typename T>
    class myClass
    {
            static_assert(
                std::is_copy_constructible<T>::value,
                "content of myClass should be copy constructible"
            );
            // TODO: check if T::operator== exists

            /* if hash<T> is available */
            using container = std::unordered_map<T,T>;
            /* if hash<T> is unavailable */
            using container = std::map<T,T>;

        public:
            myClass() = default;

            [...]

        private:
            container m_mapping;
    }

我希望自动从模板T推导出容器:

  • 如果std::hash&lt;T&gt; 可用,我们应该使用unordered_map 来提高性能。
  • 如果std::hash&lt;T&gt;不可用,我们应该回退到map

有没有办法使用 c++ 模板来做到这一点?

【问题讨论】:

  • 所以您希望编译器在不告诉您的情况下彻底改变您的应用程序的性能特征?
  • 有趣的问题。
  • @NeilButterworth 我希望编译器选择我想要的,而不必每次都指定。
  • 为什么指定类型这么麻烦?这显然比编写一些复杂的模板代码要简单得多。这是一个真正的应用程序吗?
  • 并非如此。基本上我正在编写这个助手类,它将包含在一个更大的软件中。我可能是唯一一个使用它的人,但我想设计它以便其他人可以轻松地重复使用它。所以它可能不会被使用,但我想学习现代 c++ 的魔力并知道这样的事情是否可能。

标签: c++ templates template-specialization


【解决方案1】:

这可以通过一个相当简单的帮助模板来完成:

template <typename T, typename = void>
struct helper
{
    using type = std::map<T, T>;
};

template <typename T>
struct helper<T, std::void_t<decltype(std::hash<T>())>>
{
    using type = std::unordered_map<T, T>;
};

Live demo

如果std::hash&lt;T&gt;() 格式正确,则选择该专业化,否则 SFINAE 启动并使用基本模板。然后你可以调整myClass 看起来像这样:

template<typename T>
class myClass
{
    //...
    using container = typename helper<T>::type;
    //...

private:
    //...
    container m_mapping;
    //...
};

请注意,std::void_t 是在 C++17 中添加的。如果您无法访问 C++17,您可以实现自己的 void_t

template <typename...>
using void_t = void;

【讨论】:

  • 帮手?将其命名为select_map&lt;T&gt; 怎么样?无论如何都赞成,
【解决方案2】:

如果我猜对了,它在 C++11 和更新版本中很容易实现。首先,您可以使用 type_traits 标头中的条件类型。它看起来像这样:

std::conditional<test, Type1, Type2>

如果test 为真,conditional 的内部类型 type 等于 Type1'. Otherwise, it has member type equal toType2`。

其次,我们需要上面的test 值,为此我们必须编写一个特征。 我将使用这里的示例:How to decide if a template specialization exist - 让我们保持简单。

template<class T>
bool is_hashable_v = is_complete<std::hash<T>>::value;

编辑:上面不起作用。 那么让我们使用enable_if

template<class T, class = void>
struct is_hashable : std::false_type {};

template<class T>
struct is_hashable<
    T, 
    typename std::enable_if<decltype(std::hash<T>())>::type
> : std::true_type {};

template<class T>
bool is_hashable_v = is_hashable<T>::value;

所以把它们放在一起,如果你有以上is_hashable_v

using container =
    typename std::conditional<
        is_hashable_v<T>, 
        std::unordered_map<T, T>,
        std::map<T, T> 
    >::type;

【讨论】:

  • 我不认为std::hash 的禁用专业化保证是不完整的。
  • 经过测试:它们不是:/
  • 我按照@MilesBudnek 的模型制作了一个更简单的 is_hashable 答案:模板 std::false_type is_hashable;模板 std::true_type is_hashable())>>;
【解决方案3】:

我意识到std::hash&lt;T&gt; 不是静态函数,而是带有operator() 和构造函数的结构。与其尝试检查std::hash&lt;T&gt;()(构造函数)或std::hash&lt;T&gt;()()(散列函数)是否存在,我们可以简单地检查std::hash&lt;T&gt;is_constructible是否存在

这使得以下代码有效:

using container = typename std::conditional<
    std::is_constructible<std::hash<T>>::value,
    std::unordered_map<T,T>,
    std::map<T,T>
>::type;

【讨论】:

    【解决方案4】:

    使用 C++20

    你可以使用概念:

    template<typename Key>
    concept Hashable = requires(Key a) {
        { std::hash<Key>{}(a) } -> std::convertible_to<std::size_t>;
    };
    
    template<typename Key>
    concept Sortable = requires(Key a, Key b) {
        { a < b } -> std::convertible_to<bool>;
    };
    
    template<typename Key>
    concept SortableNotHashable = Sortable<Key> && !Hashable<Key>;
    
    template <typename Key, typename Value>
    struct associative_map;
    
    template <SortableNotHashable Key, typename Value>
    struct associative_map<Key, Value>
    : std::map<Key, Value> {
        using std::map<Key, Value>::map;
    };
    
    template <Hashable Key, typename Value>
    struct associative_map<Key, Value> 
    : std::unordered_map<Key, Value> {
        using std::unordered_map<Key, Value>::unordered_map;
    };
    

    代码:https://godbolt.org/z/3GjuXt


    在 C++ 20 之前

    template <typename Key, typename Value, typename = void>
    struct associative_map
    : std::map<Key, Value> {
        using std::map<Key, Value>::map;
    };
    
    template <typename Key, typename Value>
    struct associative_map<Key, Value, std::void_t<decltype(std::hash<Key>())>> 
    : std::unordered_map<Key, Value> {
        using std::unordered_map<Key, Value>::unordered_map;
    };
    
    // Usage:
    
    class Number {
        int _i;
    public:
        Number(int i): _i(i) {}
        operator int()const {return _i;}
    };
    
    int main() {
        // associative_map below is std::unordered_map
        associative_map<int, std::string> int2string = { {1, "one"} };
        int2string[7] = "seven";
        for(const auto& p : int2string) {
            std::cout << p.first << ": " << p.second << std::endl;
        }
        // associative_map below is std::map
        associative_map<Number, std::string> number2string = { {1, "one"} };
        number2string[7] = "seven";
        for(const auto& p : number2string) {
            std::cout << p.first << ": " << p.second << std::endl;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-30
      • 2022-01-20
      • 1970-01-01
      • 1970-01-01
      • 2022-08-22
      相关资源
      最近更新 更多