【问题标题】:How to avoid using the "indices trick" repeatedly?如何避免反复使用“指数技巧”?
【发布时间】:2018-01-30 12:09:00
【问题描述】:

我有一个名为memory_region 的类,它有点像一个无类型的gsl::span(即它本质上是一个void* 和一个size_t),我也将它用于类型擦除。因此它有一个as_span<T>() 方法。

有了这个类,我有一个std::unordered_map<std::string, memory_region> my_map - 它用于在我的代码部分之间传递类型擦除的跨度,这些部分不共享标头,因此它们无法知道彼此的类型。对其中之一的典型访问如下所示:

auto foo = my_map.at("foo").as_span<bar_t>();

这适用于具有一组固定缓冲区、类型和名称的代码。但是 - 当我的代码缓冲区依赖于模板参数包时,事情就变得棘手了。现在,我已经实现了一个

std::string input_buffer_name(unsigned input_buffer_index);

函数,所以如果我有一个索引序列和我的参数包我可以做,例如

template<typename Ts..., std::size_t... Indices>
my_function(std::unordered_map<std::string, memory_region>& my map) {
    compute_stuff_with_buffers(
        my_map.at(input_buffer_name(Indices)).as_span<Ts>()...
    );
}

(这是臭名昭著的indices trick 的变体;请注意,同一类型可能会在包中出现多次,因此我不能“将类型包装在一个元组中”并按类型访问它。)

但问题是 - 我的代码在模板参数中没有那个索引序列;其中大部分仅在类型的参数包上进行模板化。所以我发现自己一直在编写“辅助函数/方法”以便能够使用该索引序列,例如:

template<typename Ts..., std::size_t... Indices>
my_function_helper(
    std::unordered_map<std::string, memory_region>& my map
    std::index_sequence<Indices...>  /* unused */) 
{
    compute_stuff_with_buffers(
        my_map.at(input_buffer_name(Indices)).as_span<Ts>()...
    );
}

template<typename Ts...>
my_function(std::unordered_map<std::string, memory_region>& my map) {
    my_function_helper(
        my_map, std::make_index_sequence<sizeof...(Ts)> {}
    );
}

我能做些什么,而不涉及那么多代码重复?

【问题讨论】:

  • 没有真正解决索引技巧,但大部分代码似乎是多余的。 unordered_mapinput_buffer_name 都不是必需的,因为您实际上是使用索引提取 memory_region 项目。所以在我看来你可以使用tuple 作为容器。此外,如果Ts pack 不包含重复类型,那么您可以使用标记元组作为容器并直接按类型从中提取项目。 compute_stuff_with_buffers(regions.at&lt;Ts&gt;().as_span&lt;Ts&gt;()...);
  • @VTT:创建my_map 的代码不知道使用my_map 的代码会对哪些类型感兴趣。此外,Ts 经常包含重复的类型。将编辑以提及最后一点。
  • @VTT:此外,代码的其他部分需要能够在不知道任何类型的情况下迭代 buffers,并且 buffers 源自不同的实例化。
  • 小请求:能否修复代码中的语法错误,并链接到可编译的示例?真的会帮助那些来到这里的人快速上手并更快地回答您的问题。

标签: c++ c++14 variadic-templates idioms index-sequence


【解决方案1】:

在这种情况下,您可以使用数组形式的简单包扩展:

template<typename... Ts>
void my_function(std::unordered_map<std::string, memory_region>& my_map) {
    using swallow = int[];
    unsigned i = 0;
    (void)swallow{0, (my_map.at(input_buffer_name(i++)).as_span<Ts>(), 0)...};
}

Demo

pack 扩展将按顺序展开([temp.variadic]),并按顺序(从左到右)求值,因为我们使用了一个花括号初始化列表(一个未使用的整数数组):[dcl.init.聚合]

当一个聚合被初始化列表初始化时 [...] 初始化列表中的元素被依次作为聚合元素的初始化。


回复:

但是如果我需要使用 input_buffer_name(i) 两次怎么办?例如如果我需要使用 { input_buffer_name(index), my_map.at(input_buffer_name(index).as_span&lt;Ts&gt;()) }

我想我们可以利用逻辑 AND 将从左到右排序 ([expr.log.and]) 的事实,并且布尔值也可以提升为 int

template<typename... Ts>
void my_function_v2(std::unordered_map<std::string, memory_region>& my_map) {
    using swallow = int[];
    unsigned i = 0;
    (void)swallow{0, ((std::cout<< input_buffer_name(i) << std::endl, true) && (my_map.at(input_buffer_name(i++)).as_span<Ts>(), true))...};
}

Demo 2

【讨论】:

  • 这行得通,所以 +1,但它现在大约是辅助函数方式的一半。
  • @einpoklum:IMO 辅助功能并没有那么糟糕。也许我们可以研究创建一个每个人都可以调用的生成辅助函数?
猜你喜欢
  • 1970-01-01
  • 2011-02-17
  • 2014-04-09
  • 1970-01-01
  • 1970-01-01
  • 2011-02-17
  • 1970-01-01
  • 2013-10-09
  • 2011-04-14
相关资源
最近更新 更多