Обращаясь к i-му члену std :: tuple, когда я не являюсь константным выражением? - PullRequest
3 голосов
/ 28 октября 2019

Предположим, у меня есть следующий шаблон функции:

template<typename T>
void mutate(T& t) { /*...*/ }

, и у меня есть следующий шаблон класса:

template<typename... Args>
class Processor {
    std::tuple<Args...> tup;

    // call mutate on the ith tuple element
    void process(size_t i) {
        mutate(std::get<i>(tup)); // ERROR
    }
}

Это не работает, потому что параметр i в process не является постоянным выражением. (Это не может быть константным выражением, потому что i неизвестно до времени выполнения)

Без изменения сигнатуры process или mutuate, есть ли какой-нибудь способ эффективно заставить эту работу работать? т.е. как я могу изменить реализацию process, чтобы она вызывала mutate для i-го члена tup?

Ответы [ 3 ]

6 голосов
/ 28 октября 2019

Вам нужно взять индекс времени выполнения и поднять его в константное выражение. Самый простой способ сделать это - просто использовать Boost.Mp11 , который поставляется с функцией только для этого:

template<typename... Args>
class Processor {
    std::tuple<Args...> tup;

    // call mutate on the ith tuple element
    void process(size_t i) {
        mp_with_index<sizeof...(Args)>(i, [&](auto I){
            mutate(std::get<I>(tup));
        });
    }
}

Что делает mp_with_index, так это принимает константное выражение дляМаксимальный размер (sizeof...(Args)) и размер среды выполнения (i), а затем вызовите ваш вызываемый (лямбда) с интегральной константой, которая является размером среды выполнения, возведенным в константу-выражение.

Это можно реализовать самостоятельно, используя std::index_sequence, создав массив указателей на функции и затем вызвав правильный:

template <size_t... Is, typename F>
decltype(auto) mp_with_index(size_t i, F f, std::index_sequence<Is...>) {
    using R = decltype(f(std::integral_constant<size_t, 0>{}));
    using P = R(*)(F&);
    static constexpr P fns[] = {
        +[](F& f) -> R { return f(std::integral_constant<size_t, Is>{}); }...
    };
    return fns[i](f);
}

template <size_t N, typename F>
decltype(auto) mp_with_index(size_t i, F f) {
    return mp_with_index(i, f, std::make_index_sequence<N>());
}

(обратите внимание, что реализация Boost.Mp11 лучше, чем эта, этопросто функционально правильно).

5 голосов
/ 28 октября 2019

С помощью некоторых вспомогательных функций мы можем это сделать.

#include <tuple>
#include <iostream>

template<typename T>
void mutate(T& t)
{
    std::cout << typeid(t).name() << "\n";
}

template<typename T, std::size_t... Seq>
void processes_each(int i, T& t, std::index_sequence<Seq...> const&)
{
    bool discard[] = {  false,
                        (i == Seq ? (mutate(std::get<Seq>(t)), true) : false) ...
                     };
    (void)discard;
}

template<typename... Args>
class Processes
{
    std::tuple<Args...> tup;

    public:
    void process(size_t i) {
        processes_each(i, tup, std::make_index_sequence<sizeof...(Args)>());
    }
};

int main()
{
    Processes<int, double>      p1;
    p1.process(0);
}
2 голосов
/ 28 октября 2019

Просто для удовольствия ...

Ожидание C ++ 20 (шаблон лямбда)

void process (std::size_t i)
 {
   [&]<std::size_t ... Is>(std::index_sequence<Is...> const &)
    { ((i == Is ? (mutate(std::get<Is>(tup)), 0) : 0), ...); }
     (std::index_sequence_for<Args...>{});
 }

в C ++ 17 мы можем использовать рекурсивную универсальную лямбду с std::integral_constant

   template <std::size_t I>
   using IC = std::integral_constant<std::size_t, I>;

   void process (std::size_t i)
    {
      auto l = [&](auto self, auto ic)
       { if constexpr ( ic < sizeof...(Args) )
            i == ic ? (mutate(std::get<ic>(tup)), 0)
                    : (self(self, IC<ic+1u>{}), 0); };

      l(l, IC<0u>{});
    }

Ниже приведен полный пример компиляции

#include <tuple>
#include <iostream>

template<typename T>
void mutate (T & t)
 { std::cout << t << "\n"; }

template<typename... Args>
struct Processes
 {
   std::tuple<Args...> tup;

   template <std::size_t I>
   using IC = std::integral_constant<std::size_t, I>;

   void process (std::size_t i)
    {
      auto l = [&](auto self, auto ic)
       { if constexpr ( ic < sizeof...(Args) )
            i == ic ? (mutate(std::get<ic>(tup)), 0)
                    : (self(self, IC<ic+1u>{}), 0); };

      l(l, IC<0u>{});
    }
};

int main()
 {
   Processes<char, int, long, long long>  p{{'0', 10, 200l, 3000ll}};

   p.process(2u);
 }
...