Как получить i-й элемент из std :: tuple, когда я не знаю во время компиляции? - PullRequest
18 голосов
/ 19 ноября 2011

У меня есть переменная i типа std::size_t и кортеж типа std::tuple.Я хочу получить i -й элемент кортежа.Я попытался это:

// bindings... is of type const T&...
auto bindings_tuple = std::make_tuple(bindings...);
auto binding = std::tuple_element<i, const T&...>(bindings_tuple);

Но я получаю эту ошибку компиляции, говоря, что первый аргумент шаблона должен быть целочисленным константным выражением:

ошибка: нетипизированный аргумент шаблона типа'std::size_t' (он же 'unsigned long') не является целочисленным константным выражением

Возможно ли получить i -й элемент кортежа и как это сделать?


Я бы хотел сделать это без использования наддува, если это возможно.

Ответы [ 4 ]

21 голосов
/ 10 февраля 2015

Это возможно:

struct Functor 
{
    template<typename T>
    void operator()(T& t) const { std::cout << t << std::endl; }
};

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
  for_index(int, std::tuple<Tp...> &, FuncT)
  { }

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
  for_index(int index, std::tuple<Tp...>& t, FuncT f)
  {
    if (index == 0) f(std::get<I>(t));
    for_index<I + 1, FuncT, Tp...>(index-1, t, f);
  }

auto t = make_tuple(1, 2, "abc", "def", 4.0f);
int i = 2; // for example
for_index(i, t, Functor());

Этот код напечатает:

abc

Рабочий образец на ideone: образец

17 голосов
/ 19 ноября 2011

Вы не можете. Это не то, для чего кортеж. Если вам нужен динамический доступ к элементу, используйте std::array<T,N>, который почти идентичен std::tuple<T,...,T>, но дает вам динамический [i] -оператор; или даже полностью динамический контейнер типа std::vector<T>.

13 голосов
/ 19 ноября 2011

Это, вероятно, не то, что хочет OP, но в любом случае, можно вернуть i -й элемент, используя время выполнения i , при условии, что вы возвращаете тип варианта, такой как boost::variant или boost::any,

#include <tuple>
#include <stdexcept>
#include <boost/variant.hpp>

template <size_t n, typename... T>
boost::variant<T...> dynamic_get_impl(size_t i, const std::tuple<T...>& tpl)
{
    if (i == n)
        return std::get<n>(tpl);
    else if (n == sizeof...(T) - 1)
        throw std::out_of_range("Tuple element out of range.");
    else
        return dynamic_get_impl<(n < sizeof...(T)-1 ? n+1 : 0)>(i, tpl);
}

template <typename... T>
boost::variant<T...> dynamic_get(size_t i, const std::tuple<T...>& tpl)
{
    return dynamic_get_impl<0>(i, tpl);
}

Например:

#include <string>
#include <iostream>

int main()
{
    std::tuple<int, float, std::string, int> tpl {4, 6.6, "hello", 7};

    for (size_t i = 0; i < 5; ++ i)
        std::cout << i << " = " << dynamic_get(i, tpl) << std::endl;

    return 0;
}

напечатает:

0 = 4
1 = 6.6
2 = hello
3 = 7
terminate called after throwing an instance of 'std::out_of_range'
  what():  Tuple element out of range.
Aborted

(boost::variant<T...> требует g ++ 4,7)

2 голосов
/ 11 июля 2014

Вопрос здесь, какой будет тип возвращаемого типа, если это будет возможно? Это должно быть известно во время компиляции, но кортеж может содержать элементы разных типов.

Предположим, у нас есть кортеж из трех элементов:

auto tuple = std::make_tuple(10, "", A());
using tuple_type = decltype(tuple);

По-видимому, получение N-го элемента не имеет особого смысла. Какой это будет тип? Это не известно до времени выполнения. Однако вместо получения N-го элемента вы можете применить к нему функцию, учитывая, что все элементы поддерживают некоторый общий протокол:

void process(int n)
{
  if (n == 0)
    func(std::get<0>(tuple));
  else if (n == 1)
    func(std::get<1>(tuple));
  else if (n == 2)
    func(std::get<2>(tuple));
}

Этот код "динамически" обрабатывает элемент, учитывая индекс n. Общий протокол в этом примере - функция func, которая может сделать что-то осмысленное со всеми возможными типами, используемыми в кортеже.

Однако написание такого кода вручную утомительно, мы хотим сделать его более общим. Давайте начнем с извлечения функции приложения, чтобы мы могли повторно использовать одну и ту же функцию process для различных функторов:

template<template<typename > class F>
void process(int n)
{
  if (n == 0)
  {
    using E = typename std::tuple_element<0, tuple_type>::type;
    F<E>::apply(std::get<0>(tuple));
  }
  else if (n == 1)
  {
    using E = typename std::tuple_element<1, tuple_type>::type;
    F<E>::apply(std::get<1>(tuple));
  }
  else if (n == 2)
  {
    using E = typename std::tuple_element<2, tuple_type>::type;
    F<E>::apply(std::get<2>(tuple));
  }
}

В этом случае F может быть реализовано как что-то вроде:

// Prints any printable type to the stdout
struct printer
{
  static void apply(E e)
  {
    std::cout << e << std::endl;
  }
}

Давайте создадим компилятор для генерации всего этого кода, давайте сделаем его универсальным:

constexpr static std::size_t arity = std::tuple_size<tuple_type>::value;
template<int N>
struct wrapper
{
  template<template<typename, typename ... > class F>
  static void apply_to(tuple_type& tuple, int idx)
  {
    if (idx)
      // Double recursion: compile and runtime.
      // Compile-time "recursion" will be terminated once
      // we reach condition N == tuple arity
      // Runtime recursion terminates once idx is zero.
      wrapper<N + 1>::template apply_to<F>(tuple, idx - 1);
    else
    {
      // idx == 0 (which means original index is equal to N).
      using E = typename std::tuple_element<N, tuple_type>::type;
      F<E>::apply(std::get<N>(tuple));
    }
  }
};

// Termination condition: N == arity.
template<>
struct wrapper<arity>
{
  template<template<typename, typename ... > class F>
  static void apply_to(tuple_type&, int)
  {
    // Throw exception or something. Index is too big.
  }
};

Использование:

wrapper<0>::template apply_to<printer>(tuple, 2);

Сделать его полностью общим - это еще одна история. По крайней мере, он должен быть независимым от типа кортежа. Затем вы, вероятно, захотите сгенерировать возвращаемый тип функтора, чтобы вы могли вернуть значимый результат. В-третьих, заставить функтор принимать дополнительные параметры.

P.S. Я не настоящий разработчик C ++, поэтому подход выше может быть полное отсутствие. Тем не менее, я нашел это полезным для моего проекта микроконтроллера, где я хочу, чтобы как можно больше было разрешено во время компиляции и все же было достаточно универсальным, чтобы я мог легко перемешать вещи. Например, «меню» в моем проекте - это, по сути, кортеж «действий», в котором каждое действие представляет собой отдельный класс, который поддерживает простой протокол, такой как «печать этикетки на текущей позиции на ЖК-дисплее» и «активация и запуск цикла пользовательского интерфейса». .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...