konwersja typu oraz obiektu std::tuple

0

Za sugestia @kq nowy watek, w nawiazaniu do poprzedniego.
Na poczatek kod, ktory udalo mi sie napisac

#include <iostream>
#include <tuple>
using namespace std;
 
template<typename A, typename B, int C>
struct base {
    static constexpr auto Value = C;
    using type_A = A;
    using type_B = B;
    int my_value() const { return C; }
};
 
template<typename T>
struct instance {
    instance () : v (0) {}
    instance (const T& v) : v(v) {}
    int v;
};
 
template<typename T, size_t N>
struct type_generator
{
    using rest_tuple = typename type_generator<T, N-1>::type;
    using current_type = instance<typename std::tuple_element<N, T>::type::type_A>;
 
    using type = decltype(
        std::tuple_cat(
            std::declval<rest_tuple>(),
            std::declval<std::tuple<current_type>>()
        )
    );
};
 
template<typename T>
struct type_generator<T, 0>
{
    using current_type = instance<typename std::tuple_element<0, T>::type::type_A>;
    using type = std::tuple<current_type>;
};
 
template<typename M>
auto instantiate(const M &m) -> typename type_generator<M, std::tuple_size<M>::value - 1>::type
{
}
 
int main() {
    tuple<base<int, float, 1>, base<float, double, 2>> a;
 
    static_assert(
        std::is_same<
            type_generator<decltype(a), tuple_size<decltype(a)>::value - 1>::type,
            tuple<instance<int>, instance<float>>
        >::value == true);
    return 0;
}

idea jest taka, ze mam tuple typow base, np std::tuple<base<A1, B1, C1>, base<A2, B2, C2> ...> i konwertuje ja na tuple std::tuple<instance<A1>, instance<A2>...> (czyli biore sobie po jednym parametrze szablonu typu base i uzywam go jako parametr szablonu typu instance). W tym momencie struktura type_generator umozliwia mi juz wygenerowanie poprawnego typu docelowego. Teraz problemem jest zainicjowanie tej tupli. Zalozylem sobie, ze klasy typu base maja jakies atrybuty, ktore chcialbym uzyc w konstruktorze typu instance. W tym przypadku to trzeci parametr szablonu, int C. Dla ulatwienia wrzucilem sobie go jakos static constexpr ale fajnie jakby mogloby to by tez byc pole nie-static, np. metoda my_value. W takim wypadku konwersja std::tuple<base<A1, B1, C1>, base<A2, B2, C2>> first_tuple musialaby wygladac std::tuple<instance<A1>, instance<A2>> second_tuple(std::get<0>(first_tuple).my_value(), std::get<1>(first_tuple).my_value()). I to byloby zadanie funkcji instantiate, ktora wlasnie teraz probuje napisac i o ktora jest pytanie. :-)

0

instance<float> zawsze ma brać pierwszy, a nie zależne od C?

0

Nie do konca rozumiem pytanie, wiec odpowiem kodem, chcialbym aby funkcja instantiate dla typu parametru std::tuple<base<int, float, 1>, base<float, double, 2>> zadzialala tak:

std::tuple<instance<int>, instance<float>> instantiate(const std::tuple<base<int, float, 1>, base<float, double, 2>> &m)
{
	return std::make_tuple(
			instance<int>(std::get<0>(m).my_value()),
			instance<float>(std::get<1>(m).my_value())
		);
}

typ zwracany, tj. std::tuple<instance<int>, instance<float>> mam juz wygenerowany wlasnie za pomoca type_generator. Mysle jak wykorzystac https://stackoverflow.com/questions/28410697/c-convert-vector-to-tuple ale nie do konca rozumiem ta konstrukcje z index_sequence oraz std::make_tuple(func(Is)...);

1

Ok, myślałem,​ że chcesz wybrać typ A lub B w zależności od parametru C. Wersja bez tego:

namespace detail
{

template<typename T>
auto base_to_instance(T&&) {
	using base_type = std::remove_const_t<std::remove_reference_t<T>>;
	return instance<typename base_type::type_A>(base_type::Value);
}

template<typename T, size_t... Is>
auto tuple_of_bases_to_tuple_of_instances(T&& t, std::index_sequence<Is...>) {
	return std::make_tuple(base_to_instance(std::get<Is>(t))...);
}
}

template<typename T>
auto tuple_of_bases_to_tuple_of_instances(T&& t) {
	using seq = std::make_index_sequence<std::tuple_size<std::decay_t<T>>::value>;
	return detail::tuple_of_bases_to_tuple_of_instances(std::forward<T>(t), seq{});
}

link. O coś takiego chodzi?

int main()
{
	tuple<base<int, float, 1>, base<float, double, 2>> a;
//	type_generator<decltype(a), 1>::type new_tuple;
	auto new_tuple = tuple_of_bases_to_tuple_of_instances(a);

	static_assert(is_same<decltype(new_tuple), tuple<instance<int>, instance<float>>>::value);
}
0

Po objawach widze, ze dziala. :-) Widze tez, ze moj type_generator byl troche przekombinowany jak na ten przypadek. Bylbys w stanie w kilku slowach/zdaniach strescic "o co chodzi z tym index_sequence" albo chociaz podlinkowac jakies sensowne wytlumaczenie "case study"? Moj mozg nie potrafi skompilowac do czego rozwija sie "std::get<Is>(t))..."

2

std::make_index_sequence<N> to alias dla std::index_sequence<0, 1, ..., N-1>1.

Mając taki index_sequence możesz przekazać do przez wartość do funkcji aby rozpakować variadic packa2. To właśnie robię przekazując seq{} do funkcji w detail::.

Dzięki temu detail::tuple_of_bases_to_tuple_of_instances ma paczkę size_t... Is, którą może wykorzystać do ekspansji paczki (pack expansion). W zależności od tego gdzie ... zostanie umieszczone, całe wyrażenie zawierające paczkę zostanie rozszerzone. Co prawda można to robić tylko w określonych kontekstach, ale generalnie tak możesz o tym myśleć:

  • Is...0, 1, 2, ..., N-1
  • std::get<Is>(foo)...std::get<0>(foo), std::get<1>(foo), std::get<2>(foo), std::get<...>(foo), std::get<N-1>(foo)
  • base_to_instance(std::get<Is>(foo))base_to_instance(std::get<0>(foo)), base_to_instance(std::get<1>(foo)), base_to_instance(std::get<2>(foo)), base_to_instance(std::get<...>(foo)), base_to_instance(std::get<N-1>(foo))

Potem pakuję wyniki tych funkcji (czyli instancje instance<X>) do wywołania std::make_tuple i jestem w domu.

1jest też integer_sequence - prawie to samo, tylko bardziej ogólne
2przepraszam za Ponglish, ale nie widzę jak to sensownie napisać

0

Wielkie dzieki. :-) Zbyt dlugo od C++ odszedlem i czasem takie konstrukcje sprawiaja mi problemy.

1 użytkowników online, w tym zalogowanych: 0, gości: 1