В C ++ возможно ли получить тип одного элемента кортежа, когда индекс элемента известен во время выполнения? - PullRequest
1 голос
/ 07 ноября 2011
typedef std::tuple< int, double > Tuple;
Tuple t;
int a = std::get<0>(t);
double b = std::get<1>(t);

for( size_t i = 0; i < std::tuple_size<Tuple>::value; i++ ) {
   std::tuple_element<i,Tuple>::type v = std::get<i>(t);// will not compile because i must be known at compile time
}

Я знаю, что можно написать код для получения std::get работы (см., Например, повторение над кортежем ), возможно ли получить std::tuple_element тоже работу?1007 * Некоторые ограничения (их можно ослабить):

без шаблонов с переменными параметрами, без Boost

Ответы [ 4 ]

7 голосов
/ 07 ноября 2011

C ++ - типизированный язык времени компиляции. У вас не может быть типа, который компилятор C ++ не может определить во время компиляции.

Вы можете использовать полиморфизм различных форм, чтобы обойти это. Но в конце дня каждая переменная должна иметь четко определенный тип. Поэтому, хотя вы можете использовать алгоритмы Boost.Fusion для перебора переменных в кортеже, вы не можете иметь цикл, в котором каждое выполнение цикла может использовать тип, отличный от последнего.

Единственная причина, по которой Boost.Fusion может сойти с рук, заключается в том, что он не использует цикл. Он использует рекурсию шаблона для «перебора» каждого элемента и вызова предоставляемой пользователем функции.

4 голосов
/ 07 ноября 2011

Если вы хотите обойтись без повышения, ответы на итерирование по кортежу уже расскажут вам все, что вам нужно знать. Вы должны написать цикл for_each времени компиляции (непроверенный).

template<class Tuple, class Func, size_t i>
void foreach(Tuple& t, Func fn) {
    // i is defined at compile-time, so you can write:
    std::tuple_element<i, Tuple> te = std::get<i>(t);
    fn(te);
    foreach<i-1>(t, fn);
}

template<class Tuple, class Func>
void foreach<0>(Tuple& t, Func fn) { // template specialization
    fn(std::get<0>(t)); // no further recursion
}

и используйте его так:

struct SomeFunctionObject {
    void operator()( int i ) const {}
    void operator()( double f ) const {}
};

foreach<std::tuple_size<Tuple>::value>(t, SomeFunctionObject());

Однако, если вы хотите перебрать членов кортежа, Boost.Fusion действительно подходит.

#include <boost/fusion/algorithm/iteration/for_each.hpp>
#include <boost/fusion/adapted/boost_tuple.hpp>

и в своем коде напишите:

boost::for_each(t, SomeFunctionObject());

Это пример для boost :: tuple. Здесь есть адаптер для boost :: fusion для работы с std :: tuple: http://groups.google.com/group/boost-list/browse_thread/thread/77622e41af1366af/

1 голос
/ 15 ноября 2012

Вот мой кортеж / функция преобразования tuple:

#include <cstddef>
#include <tuple>
#include <type_traits>

template<size_t N>
struct tuple_foreach_impl {
    template<typename T, typename C>
    static inline auto call(T&& t, C&& c)
        -> decltype(::std::tuple_cat(
            tuple_foreach_impl<N-1>::call(
                ::std::forward<T>(t), ::std::forward<C>(c)
            ),
            ::std::make_tuple(c(::std::get<N-1>(::std::forward<T>(t))))
        ))
    {
        return ::std::tuple_cat(
            tuple_foreach_impl<N-1>::call(
                ::std::forward<T>(t), ::std::forward<C>(c)
            ),
            ::std::make_tuple(c(::std::get<N-1>(::std::forward<T>(t))))
        );
    }
};

template<>
struct tuple_foreach_impl<0> {
    template<typename T, typename C>
    static inline ::std::tuple<> call(T&&, C&&) { return ::std::tuple<>(); }
};

template<typename T, typename C>
auto tuple_foreach(T&& t, C&& c)
    -> decltype(tuple_foreach_impl<
        ::std::tuple_size<typename ::std::decay<T>::type
    >::value>::call(std::forward<T>(t), ::std::forward<C>(c)))
{
    return tuple_foreach_impl<
        ::std::tuple_size<typename ::std::decay<T>::type>::value
    >::call(::std::forward<T>(t), ::std::forward<C>(c));
}

В примере использования используется следующая утилита, позволяющая печатать кортежи в ostreams:

#include <cstddef>
#include <ostream>
#include <tuple>
#include <type_traits>

template<size_t N>
struct tuple_print_impl {
    template<typename S, typename T>
    static inline void print(S& s, T&& t) {
        tuple_print_impl<N-1>::print(s, ::std::forward<T>(t));
        if (N > 1) { s << ',' << ' '; }
        s << ::std::get<N-1>(::std::forward<T>(t));
    }
};

template<>
struct tuple_print_impl<0> {
    template<typename S, typename T>
    static inline void print(S&, T&&) {}
};

template<typename S, typename T>
void tuple_print(S& s, T&& t) {
    s << '(';
    tuple_print_impl<
        ::std::tuple_size<typename ::std::decay<T>::type>::value
    >::print(s, ::std::forward<T>(t));
    s << ')';
}

template<typename C, typename... T>
::std::basic_ostream<C>& operator<<(
    ::std::basic_ostream<C>& s, ::std::tuple<T...> const& t
) {
    tuple_print(s, t);
    return s;
}

И, наконец, пример использования:

#include <iostream>

using namespace std;

struct inc {
    template<typename T>
    T operator()(T const& val) { return val+1; }
};

int main() {
    // will print out "(7, 4.2, z)"
    cout << tuple_foreach(make_tuple(6, 3.2, 'y'), inc()) << endl;
    return 0;
}

Обратите внимание, что вызываемый объект сконструирован так, что он может сохранять состояние при необходимости. Например, вы можете использовать следующее, чтобы найти последний объект в кортеже, который может быть динамически приведен к T:

template<typename T>
struct find_by_type {
    find() : result(nullptr) {}
    T* result;
    template<typename U>
    bool operator()(U& val) {
        auto tmp = dynamic_cast<T*>(&val);
        auto ret = tmp != nullptr;
        if (ret) { result = tmp; }
        return ret;
    }
};

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

Edit: Я только что понял, что средство записи кортежей можно было тривиально написать с помощью функции foreach (у меня был код печати кортежей гораздо дольше, чем код foreach).

template<typename T>
struct tuple_print {
    print(T& s) : _first(true), _s(&s) {}
    template<typename U>
    bool operator()(U const& val) {
        if (_first) { _first = false; } else { (*_s) << ',' << ' '; }
        (*_s) << val;
        return false;
    }
private:
    bool _first;
    T* _s;
};

template<typename C, typename... T>
::std::basic_ostream<C> & operator<<(
    ::std::basic_ostream<C>& s, ::std::tuple<T...> const& t
) {
    s << '(';
    tuple_foreach(t, tuple_print< ::std::basic_ostream<C>>(s));
    s << ')';
    return s;
}
1 голос
/ 07 ноября 2011

Нет, это не так, как вы это описываете. По сути, вам нужно написать свой код для каждого возможного значения времени выполнения i, а затем использовать некоторую диспетчерскую логику (например, switch(i)) для запуска правильного кода на основе фактического значения времени выполнения i.

На практике может быть возможно сгенерировать код для различных значений i с помощью шаблонов, но я не совсем уверен, как это сделать, и будет ли это практичным. То, что вы описываете, звучит как некорректный дизайн.

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