【问题标题】:Variadic template parameter inference with nested maps使用嵌套映射的可变模板参数推断
【发布时间】:2020-12-04 12:17:08
【问题描述】:

在使用 C++ 时,有时我会使用嵌套映射。例如,假设:

enum Color { RED, GREEN, BLUE};
enum Shape { CIRCLE, SQUARE, TRIANGLE };
std::unordered_map<Color, std::unordered_map<Shape, int>> shapeColorCount;

对于这些情况,使用可变参数模板编写在键类型上模板化的 setter 和 getter 函数会很有用。目标是这样的:

template<typename TValue, typename TKeys...>
TValue& nestedGet(MapTypeHere t_map, const TKeys&... t_keys);
void nestedSet(MapTypeHere t_map, const TValue& t_value, const TKeys&... t_keys);

递归地定义这些函数并不难,但我的主要问题是让模板参数的类型推断正常工作。问题是指定 MapTypeHere。我几乎可以写出类似的东西

template<typename TValue, typename TKey>
using Map = std::unordered_map<TKey, TValue>;

template<typename TValue, typename TOuterKey, typename... TInnerKeys>
using Map = std::unordered_map<TOuterKey, Map<TValue, TInnerKeys...>;

template<typename TValue, typename... TKeys>
TValue& nestedGet(Map<TValue, TKeys...>& t_map, const TKeys&... t_keys);
void nestedSet(Map<TValue, TKeys...>& t_map, const TValue& t_value, const TKeys&... t_keys);

尝试创建递归 using 指令,但它抱怨我在尝试使用 Map 的基本情况时尝试在非包模板变量中使用参数包。如果我将它们包装在结构中,它似乎允许它使用声明执行此递归,但是我遇到了类型推断不起作用的问题。回到上面的例子:

std::unordered_map<Color, std::unordered_map<Shape, int>> shapeColorCount
nestedSet<int, Color, Shape>(shapeColorCount, 5, Color::RED, Shape::SQUARE); // This works
nestedSet(shapeColorCount, 5, Color::RED, Shape::SQUARE); // It can't figure out the types for the template

有没有办法让这个设置正常工作?

【问题讨论】:

    标签: c++ templates variadic-templates type-inference template-argument-deduction


    【解决方案1】:

    是的,你可以编写如下函数:

    template<typename Map, typename Value, typename FirstKey, typename ...RestKeys>
    void nestedSet(Map& map, Value const& value, FirstKey const& key, RestKeys const&... rest_keys)
    {
        if constexpr(sizeof...(RestKeys) == 0)
            map[key] = value;
        else
            nestedSet(map[key], value, rest_keys...);
    }
    
    template<typename Map, typename FirstKey, typename ...RestKeys>
    auto& nestedGet(Map& map, FirstKey const& key, RestKeys const&... rest_keys)
    {
        if constexpr(sizeof...(RestKeys) == 0)
            return map[key];
        else
            return nestedGet(map[key], rest_keys...);
    }
    

    请注意,此解决方案不依赖于特定的 unordered_map&lt;Color, std::unordered_map&lt;Shape, int&gt;&gt; 实例化。它适用于键和值类型的任何实例化,以及任何深度的嵌套unordered_maps。

    这是demo

    另外,如果您没有 c++17,那么您可以使用带有单个 KeyType 参数的重载模板重写 if constexpr 解决方案。

    【讨论】:

    • 有趣。建议我通过尝试直接构造类型 Map 来做一些过于复杂的事情,因为原则上它可以从 FirstKey、RestKeys 和 Value 中推断出来,而不是直接使用 Map。有什么方法可以构造静态断言来确保 Map 具有正确的内部结构?我想如果您尝试使用不匹配的映射结构和参数调用该函数,它会在某些时候无法实例化,例如尝试在原语上调用 operator[] 或使用不正确的变量类型。
    • 是的,无论哪种方式,您都会收到一条错误消息。如果需要自定义错误消息,可以编写 static_asserts。
    【解决方案2】:

    来不及玩了?

    在我看来,给定一个递归 nestedGet(),您可以使用 if constexpr(如果您可以使用 C++17)编写,也可以使用如下重载,

    template <typename M, typename K>
    auto & nestedGet (M & map, K const & key)
     { return map[key]; }
    
    template <typename M, typename K, typename ... RKs>
    auto & nestedGet (M & map, K const & key, RKs const & ... rks)
     { return nestedGet(map[key], rks...); }
    

    nestedSet() 函数可以写在nestedGet() 上,简单如下

    template <typename M, typename V, typename ... Ks>
    void nestedSet (M & map, V const & value, Ks const & ... keys)
     { nestedGet(map, keys...) = value; }
    

    以下是完整的编译示例

    #include <iostream>
    #include <unordered_map>
    
    enum Color { RED, GREEN, BLUE};
    enum Shape { CIRCLE, SQUARE, TRIANGLE };
    
    template <typename M, typename K>
    auto & nestedGet (M & map, K const & key)
     { return map[key]; }
    
    template <typename M, typename K, typename ... RKs>
    auto & nestedGet (M & map, K const & key, RKs const & ... rks)
     { return nestedGet(map[key], rks...); }
    
    template <typename M, typename V, typename ... Ks>
    void nestedSet (M & map, V const & value, Ks const & ... keys)
     { nestedGet(map, keys...) = value; }
    
    int main () 
     {
       std::unordered_map<Color, std::unordered_map<Shape, int>> shapeColorCount;
    
       nestedSet(shapeColorCount, 42, Color::RED, Shape::SQUARE); 
    
       std::cout << nestedGet(shapeColorCount, Color::RED, Shape::SQUARE) << std::endl; 
     }
    

    【讨论】:

    • 永远不会太晚 :) 这看起来很棒。您能否添加一个演示以便更容易看到它的工作原理?看起来不错,但有一个演示会很好。
    • @cigien - 为什么不呢?已添加示例。
    • 这似乎也可以。在提出这个问题之前,我尝试过的一个失败版本是基于一组非常相似的 getter 和 setter,但没有将类型 M 作为模板参数。我一直在想“啊,但是一旦你知道了 V 和 Ks,肯定应该可以确定 M 的有效值”,这在原则上是可能的,但对于我的目的而言实际上并不是必需的。谢谢!
    • @NathanPierson - 确切地说:鉴于您只需在其中/从中设置/获取值,因为您足以将其作为泛型类型拦截。这样,您的 getter/setter 也适用于 std::mapstd::vectorstd::arraystd::deque,不仅适用于 std::unordered_map
    猜你喜欢
    • 2023-03-24
    • 2016-10-05
    • 1970-01-01
    • 2013-10-09
    • 1970-01-01
    • 1970-01-01
    • 2022-11-01
    • 1970-01-01
    • 2022-01-11
    相关资源
    最近更新 更多