4

I have a class named memory_region, which is sort of like an untyped gsl::span (i.e. it's essentially a void* and a size_t), which I also use for type erasure. It thus has an as_span<T>() method.

With this class, I have a std::unordered_map<std::string, memory_region> my_map - which is used to pass type-erased spans between parts of my code which don't share headers, so they can't know about each others' types. The typical access to one of these looks like:

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

This works just fine with code that has a fixed set of buffers and types and names. But - things get tricky when my code's buffers depend on a template parameter pack. Now, I've implemented a

std::string input_buffer_name(unsigned input_buffer_index);

function, so if I have an index sequence and my parameter pack I can do, for example

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>()...
    );
}

(this is a variation on the infamous indices trick; note that the same type may appear more than once in the pack, so I can't "wrap the types in a tuple" and acces it by type.)

The thing is, though - my code doesn't have that index sequence in the template parameters; most of it is templated on just the parameter pack of types. So I find myself writing "helper functions/methods" all the time to be able to use that index sequence, e.g.:

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)> {}
    );
}

What can I do instead, that will not involve so much code duplication?

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • Not really addressing indices trick, but most of this code seems to be redundant. Neither `unordered_map` or `input_buffer_name` are necessary as you are essentially extract `memory_region` items using index. So it seems to me that you can use `tuple` as container. Moreover, if `Ts` pack does not contain duplicated types then you can use tagged tuple as container and extract items from it directly by type. `compute_stuff_with_buffers(regions.at().as_span()...);` – user7860670 Aug 22 '17 at 10:08
  • @VTT: The code which creates `my_map` doesn't know which types the code which uses `my_map` is going to be interested in. Also, Ts often contains duplicate types. Will edit to mention this last point. – einpoklum Aug 22 '17 at 10:25
  • @VTT: Also, other parts of the code need to be able to iterate through `buffers` without knowing the types of anything, and with `buffers` originating from different instantiations. – einpoklum Aug 22 '17 at 10:35
  • 1
    Small request: Can you fix the syntax errors in your code, and also link to a compileable example? Will really help those coming here to quickly get up to speed and answer your question faster. – AndyG Aug 22 '17 at 12:15

1 Answers1

2

In this case you can use simple pack expansion in the form of an array:

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

The pack expansion will be expanded in order ([temp.variadic]), and also evaluated in order (left to right) because we're using a braced initializer list (an unused integer array): [dcl.init.aggr]

When an aggregate is initialized by an initializer list [...] the elements of the initializer list are taken as initializers for the elements of the aggregate, in order.


Re:

But what if I need to use input_buffer_name(i) twice? e.g. if I need to use { input_buffer_name(index), my_map.at(input_buffer_name(index).as_span<Ts>()) }

I suppose we could take advantage of the fact that logical AND will sequence left to right ([expr.log.and]), and also a boolean can be promoted to 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

AndyG
  • 39,700
  • 8
  • 109
  • 143
  • This works, so +1, but it now about about half as clunky as the helper function way. – einpoklum Aug 22 '17 at 13:30
  • @einpoklum: IMO the helper function is not so terrible. Maybe we could investigate creating a generate helper function that everyone can call? – AndyG Aug 22 '17 at 13:31