11

I have the following class:

template<typename... Tkeys>
class C
{
public:
    std::tuple<std::unordered_map<Tkeys, int>... > maps;

    // Not real function:
    void foo(Tkeys... keys) {
        maps[keys] = 1;
    }
};

How would I implement foo so that it assigns to each std::map in maps gets called with it matching key?

For example, if I have

C<int, int, float, std::string> c;

and I called

c.foo(1, 2, 3.3, "qwerty")

then c.maps should be equivalent to

m1 = std::map<int, int>()
m1[1] = 1;
m2 = std::map<int, int>()
m2[2] = 1;
m3 = std::map<float, int>()
m3[3.3] = 1;
m4 = std::map<std::string, int>()
m4["qwerty"] = 1;
c.maps = std::make_tuple(m1, m2, m3, m4);
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
Baruch
  • 20,590
  • 28
  • 126
  • 201

3 Answers3

8
#include <unordered_map>
#include <utility>
#include <tuple>
#include <cstddef>

template <typename... Tkeys>
class C
{
public:
    std::tuple<std::unordered_map<Tkeys, int>... > maps;

    template <typename... Args>
    void foo(Args&&... keys)
    {
        foo_impl(std::make_index_sequence<sizeof...(Args)>{}, std::forward<Args>(keys)...);
    }

private:
    template <typename... Args, std::size_t... Is>
    void foo_impl(std::index_sequence<Is...>, Args&&... keys)
    {
        using expand = int[];
        static_cast<void>(expand{ 0, (
            std::get<Is>(maps)[std::forward<Args>(keys)] = 1
        , void(), 0)... });
    }
};

DEMO

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • Thanks. I changed you answer a bit (functionally the same, just makes the fact that the function args must match the class args clearer IMO: http://coliru.stacked-crooked.com/a/6c4e636570c53665 – Baruch May 23 '16 at 12:14
  • Can you explain some of the magic, though? I am having trouble wrapping my head around `index_sequence` and the different type of variadic template unpackings – Baruch May 23 '16 at 12:15
  • @baruch You changed the behavior - Piotr's answer uses forwarding arguments, yours only accept rvalue reference. – Holt May 23 '16 at 12:16
  • @Holt Can you please show me an example that would cause a different behavior? And explain why? Thanks – Baruch May 23 '16 at 12:17
  • @baruch Try using a variable instead of an integral constant when callling `c.foo(...)`, e.g. `int a = 1; c.foo(a, 2, 3.3, "qwerty");` and your code won't compile. – Holt May 23 '16 at 12:19
  • @baruch if you want to disable this overload, use [this implementation](http://coliru.stacked-crooked.com/a/23d4a69c387d0537). alternatively, shift the condition from enable_if to a static assert – Piotr Skotnicki May 23 '16 at 12:20
  • @PiotrSkotnicki In your code, you are using `0, (..., void(), 0)`, since the type you are assigning is already `int`, couldn't you remove the last `void(), 0` (in this specific case)? I understand the purpose of `void()` is to avoid overloaded comma operator (correct me if I am wrong) if the assigned type was not a simple `int`, but what is the purpose of the first `0`? – Holt May 23 '16 at 12:24
  • 1
    @Holt indeed, the trailing `void(), 0` is not necessary, as along as the map stores `int`s. the first `0` is to protect again `foo()` calls, that would lead to a zero-size array – Piotr Skotnicki May 23 '16 at 12:28
  • @Holt I see that using an lvalue doesn't work with my change (I think I even understand why). On the other hand, calling `foo` with `1.1` for the first argument generates a warning in my version, while is fine in Piotr's. Is there a way to get both rvalues and non-silent conversions? – Baruch May 23 '16 at 12:32
  • @PiotrSkotnicki Your last version went completely over my head. Which overload exactly are you disabling here? – Baruch May 23 '16 at 12:33
  • @Holt , Piotr I would appreciate it if one of you could explain what is going on here. I copied it and it works great, but I have no idea why. – Baruch May 23 '16 at 12:36
  • @baruch wrt. disabling overloads. since you don't have other overloads, and perpahs neither you want to make it sfinae-friendly, you can juse put the condition in a static assert that will trigger an error each time the arguments are not assignable to keys, [like here](http://coliru.stacked-crooked.com/a/9d6aea64d663e58d) – Piotr Skotnicki May 23 '16 at 12:41
  • @PiotrSkotnicki according to the question you kinked to, shouldn't it be `expand{ 0, (void(std::get(maps)[std::forward(keys)] = 1), 0)... }` i.e. the cast-to-void on the left of the comma operator? – Baruch May 23 '16 at 18:54
  • @baruch `meow, void(), 0` is sufficient to avoid overloaded comma interference between `meow` and `0` – Piotr Skotnicki May 23 '16 at 18:56
  • @PiotrSkotnicki but what about the comma operator between `meow` and `void()`? Wouldn't it be interpreted as the comma operator of `meow` with an operand of type `void`? Come to think of it, I am not even sure what an operand of type `void` means. What does happen when I do `SomeClassWithOverloadedCommaOperator(), void();`? – Baruch May 23 '16 at 19:00
  • 1
    @baruch you can't have an overloaded comma operator taking a parameter of type `void`, so definitely `SomeClassWithOverloadedCommaOperator` will not interfere with `void`, neither with anything that follows `void`. itself `void()` is a no-op. – Piotr Skotnicki May 23 '16 at 19:04
3

If you have a compiler that supports C++17 fold expressions then you could have the following simple variadic expansion scheme:

template<typename... Tkeys>
class C {
    template<typename... Args, std::size_t... I>
    void foo_helper(std::index_sequence<I...>, Args&& ...args) {
      ((std::get<I>(maps)[std::forward<Args>(args)] = 1), ...);
    }
public:
    std::tuple<std::unordered_map<Tkeys, int>... > maps;

    void foo(Tkeys... keys) {
      foo_helper(std::index_sequence_for<Tkeys...>{}, std::forward<Tkeys>(keys)...);
    }
};

Live Demo

101010
  • 41,839
  • 11
  • 94
  • 168
0

For current C++ (2014) just use classic pattern with sentinel function:

public:

void foo(Tkeys... keys) {
    foo_range<0>(keys...);
}

private:

template <std::size_t I, typename F, typename... N>
void foo_range(F first, N... next) {
    std::get<I>(maps)[first] = 1;
    foo_range<I + 1>(next...);
}

template <std::size_t I>
void foo_range() {
    static_assert(I == sizeof...(Tkeys), "That should be sentinel");
}
PiotrNycz
  • 23,099
  • 7
  • 66
  • 112