2

Suppose you have std::tuple and a function, which modifies on of its values based on the type.

template<typename T, typename Ts...>
void modify(std::tuple<Ts...>& tuple, const T& value)
{
    std::get<T>(tuple) = value;
}

Now suppose, you want to log those function calls, i.e. remember the order of modification of values in the tuple. Here is simple mechanism that does that.

void log(std::size_t index)
{
    static std::vector<std::size_t> logged_indexes;
    logged_indexes.push_back(index)
}

with following helper

// counts until T is same as N'th type of Tuple
template<typename Tuple, typename T, std::size_t N>
struct tuple_type_index_impl;

template<typename T, std::size_t N>
struct tuple_type_index_impl<std::tuple<>, T, N>
{
    static constexpr std::size_t value = N;
};

template<typename First, typename... Rest, typename T, std::size_t N>
struct tuple_type_index_impl<std::tuple<First, Rest...>, T, N>
{
    static constexpr std::size_t value = std::is_same<First, T>::value
        ? N
        : tuple_type_index_impl<std::tuple<Rest...>, T, N + 1>::value;
};

// helper variable
template<typename Tuple, Typename T>
constexpr std::size_t tuple_type_index_v = typename tuple_type_index_impl<Tuple, T, 0>::value;

we can have this usage

template<typename T, typename Ts...>
void modify(std::tuple<Ts...>& tuple, const T& value)
{
    std::get<T>(tuple) = value;
    log(tuple_type_index_v<std::tuple<Ts...>, T>);
}

Which is fine for logging purposes. But suppose you would have to, based on the index, access the tuple again. We can convert runtime index to constexpr using a switch

switch (value)
{
case 0: return std::get<0>(tuple);
case 1: return std::get<1>(tuple);
...
}

which can be compiler generated. But is it really the best we can do? In call to log, the index is still constexpr, so we could move that to template parameter.

template<std::size_t index>
void log()
{
    static std::vector<std::size_t> logged_indexes;
    logged_indexes.push_back(index);
}

But that won't help, since we are still passing it to the vector. We could (?) sort of build std::index_sequence, but we would eventually run out of template parameters. Part of me thinks that this can't be done, but the other opposes, since index is constexpr. Is it possible to develop some kind of std::vector<constexpr std::size_t>?

cpplearner
  • 13,776
  • 2
  • 47
  • 72
Zereges
  • 5,139
  • 1
  • 25
  • 49
  • Is this switch or its replacement meant to be inside the logging function? e.g. you want to log the value as well as the index? – jwimberley May 16 '17 at 17:22
  • @jwimberley I don't mind the values, just indexes of accessed values. The switch is *generated* by the compiler using similar approach as showed for example [here](http://stackoverflow.com/questions/28997271/c11-way-to-index-tuple-at-runtime-without-using-switch). – Zereges May 16 '17 at 18:43
  • You can change `tuple_type_index_v` to return `std::integral_constant`, so you still have compile time value. – Jarod42 May 16 '17 at 19:07
  • @Jarod42 `tuple_type_index_v` is compile time value. – Zereges May 16 '17 at 19:18
  • Do you want retrieve tuple element from vector content, or from `log` function ? – Jarod42 May 16 '17 at 19:33
  • @Jarod42 I don't understand the question – Zereges May 16 '17 at 19:47
  • Not sure what you want, I modified you sample: [Demo](https://ideone.com/2ciE2A) – Jarod42 May 16 '17 at 21:01
  • @Jarod42 Idea is to store accessed indexes (as you do in `log`) and then retrieve them back, but as compile time constants, so that they can be passed directly to `std::get` without need of that switch (finding correct compile time index based on runtime index is O(n)) – Zereges May 17 '17 at 09:27
  • The switch can be done/implemented in `O(1)`. And you can even do the array yourself to guaranty the `O(1)`. – Jarod42 May 17 '17 at 11:02
  • @Jarod42 Can you post an answer? I am using recursive visitor pattern as show in [here](http://stackoverflow.com/questions/28997271/c11-way-to-index-tuple-at-runtime-without-using-switch) and I think it's O(n). – Zereges May 17 '17 at 11:08

1 Answers1

1

To transform your switch to "direct" call, you may do something like:

template <typename ... Ts>
void print(const std::tuple<Ts...>& t, std::size_t index)
{
    if (sizeof...(Ts) < index) {
        throw std::runtime_error("invalid index");
    }
    using func_t = void (*)(const std::tuple<Ts...>&);
    func_t fs[] = { (+[](const std::tuple<Ts...>& t){
             std::cout << std::get<Ts>(t) << std::endl;
        })...
        // gcc dislikes that lambda,
        // but you can create template function instead
    };

    fs[index](t);
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • I actually tested it and the performance is same as with switch, so compiler can somehow optimize that. – Zereges May 17 '17 at 13:21