C++: преобразовать кортеж в тип T
Я пытаюсь сделать класс под названием tuple_cnv
с (неявным) оператором преобразования для создания любого объекта из кортежа (например, C++17 std::make_from_tuple
функции), но рекурсивного характера таким образом, что, если кортеж состоит из других кортежей, он преобразует любой "внутренний кортеж" в tuple_cnv
чтобы разрешить рекурсивную конструкцию на месте целевого типа:
#include <iostream>
#include <utility>
#include <tuple>
#include <functional>
struct A { int i1, i2, i3; };
struct B { A a1, a2; };
template<class T> struct tuple_cnv;
template<class... Ts>
struct tuple_cnv<std::tuple<Ts...> >
{
using tuple_t = std::tuple<Ts...>;
std::reference_wrapper<tuple_t const> ref;
tuple_cnv(tuple_t const& t) : ref(t) {}
template<class T>
operator T() const
{ return p_convert<T>(std::index_sequence_for<Ts...>{}); }
private:
template<class T>
static T const& p_convert(T const& t) { return t; }
template<class... Tss>
static tuple_cnv<Tss...> p_convert(std::tuple<Tss...> const& t)
{ return tuple_cnv<std::tuple<Tss...> >(t); }
template<class T, std::size_t... I>
T p_convert(std::index_sequence<I...>) const
{ return {p_convert(std::get<I>(ref.get()))...}; }
};
template<class T>
auto make_tuple_cnv(T const& t) { return tuple_cnv<T>(t); }
using tup = std::tuple<std::tuple<int, int, int>, std::tuple<int, int, int> >;
int main()
{
tup t{{3, 4, 5}, {1, 7, 9}};
// Equivalent to: B b{{3,4,5}, {1,7,9}};
B b = make_tuple_cnv(t);
std::cout << b.a2.i3 << std::endl;
}
В случае сомнений, строка:
{p_convert(std::get<I>(ref.get()))...}
должен развернуть кортеж в списке его элементов через запятую (внутри {...}
получить список инициализатора), но заменяя каждый элемент кортежа соответствующим tuple_cnv
разрешить создание дерева списков инициализаторов через (неявный) оператор преобразования каждого внутреннего tuple_cnv
при строительстве объекта T
,
Смотрите закомментированное выражение "предполагаемый эквивалент" внутри main
функция.
Дело в том, что я получаю настолько большую ошибку компилятора, что не могу понять, что не так с моей реализацией:
main.cpp: In instantiation of 'T tuple_cnv<std::tuple<_Tps ...> >::p_convert(std::index_sequence<I ...>) const [with T = B; long unsigned int ...I = {0, 1}; Ts = {std::tuple<int, int, int>, std::tuple<int, int, int>}; std::index_sequence<I ...> = std::integer_sequence<long unsigned int, 0, 1>]':
main.cpp:28:26: required from 'tuple_cnv<std::tuple<_Tps ...> >::operator T() const [with T = B; Ts = {std::tuple<int, int, int>, std::tuple<int, int, int>}]'
main.cpp:53:27: required from here
main.cpp:40:51: error: could not convert '{tuple_cnv<std::tuple<std::tuple<int, int, int>, std::tuple<int, int, int> > >::p_convert<std::tuple<int, int, int> >((* & std::get<0, std::tuple<int, int, int>, std::tuple<int, int, int> >((* &((const tuple_cnv<std::tuple<std::tuple<int, int, int>, std::tuple<int, int, int> > >*)this)->tuple_cnv<std::tuple<std::tuple<int, int, int>, std::tuple<int, int, int> > >::ref.std::reference_wrapper<const std::tuple<std::tuple<int, int, int>, std::tuple<int, int, int> > >::get())))), tuple_cnv<std::tuple<std::tuple<int, int, int>, std::tuple<int, int, int> > >::p_convert<std::tuple<int, int, int> >((* & std::get<1, std::tuple<int, int, int>, std::tuple<int, int, int> >((* &((const tuple_cnv<std::tuple<std::tuple<int, int, int>, std::tuple<int, int, int> > >*)this)->tuple_cnv<std::tuple<std::tuple<int, int, int>, std::tuple<int, int, int> > >::ref.std::reference_wrapper<const std::tuple<std::tuple<int, int, int>, std::tuple<int, int, int> > >::get()))))}' from '<brace-enclosed initializer list>' to 'B'
{ return {p_convert(std::get<I>(ref.get()))...}; }
^
О чем эта ошибка компилятора? Что я не вижу?
ПРИМЕЧАНИЕ. Следуя предложению @Barry, я изменил реализацию, используя apply
, но называется tuple_to_args
вместо этого, потому что реализация не полностью эквивалентна (std::apply
использования std::invoke
что касается различных видов функций, таких как указатель на функции-члены):
template<class... Ts>
constexpr auto indexes(std::tuple<Ts...> const&)
{ return std::index_sequence_for<Ts...>{}; }
template<class fun_t, class tuple_t, std::size_t... I>
decltype(auto) tuple_to_args(fun_t&& f, tuple_t&& tuple, std::index_sequence<I...> const&)
{ return f(std::get<I>(std::forward<tuple_t>(tuple))...); }
template<class fun_t, class tuple_t>
decltype(auto) tuple_to_args(fun_t&& f, tuple_t&& t)
{ return tuple_to_args(std::forward<fun_t>(f), std::forward<tuple_t>(t), indexes(t)); }
И используя tuple_to_args
В качестве вспомогательной функции реализация оператора преобразования была изменена на:
template<class T>
operator T() const
{
auto inner_f = [](auto&&... tuple) -> T {
return {p_convert(std::forward<decltype(tuple)>(tuple))...};
};
return tuple_to_args(inner_f, ref.get());
}
Нестатический p_convert
Функция также была удалена, но ошибка компилятора все еще очень похожа:
main.cpp: In instantiation of 'tuple_cnv<std::tuple<_Tps ...> >::operator T() const::<lambda(auto:1&& ...)> [with auto:1 = {const std::tuple<int, int, int>&, const std::tuple<int, int, int>&}; T = B; Ts = {std::tuple<int, int, int>, std::tuple<int, int, int>}]':
main.cpp:15:11: required from 'decltype(auto) tuple_to_args(fun_t&&, tuple_t&&, std::index_sequence<I ...>&) [with fun_t = tuple_cnv<std::tuple<_Tps ...> >::operator T() const [with T = B; Ts = {std::tuple<int, int, int>, std::tuple<int, int, int>}]::<lambda(auto:1&& ...)>&; tuple_t = const std::tuple<std::tuple<int, int, int>, std::tuple<int, int, int> >&; long unsigned int ...I = {0, 1}; std::index_sequence<I ...> = std::integer_sequence<long unsigned int, 0, 1>]'
main.cpp:19:23: required from 'decltype(auto) tuple_to_args(fun_t&&, tuple_t&&) [with fun_t = tuple_cnv<std::tuple<_Tps ...> >::operator T() const [with T = B; Ts = {std::tuple<int, int, int>, std::tuple<int, int, int>}]::<lambda(auto:1&& ...)>&; tuple_t = const std::tuple<std::tuple<int, int, int>, std::tuple<int, int, int> >&]'
main.cpp:38:29: required from 'tuple_cnv<std::tuple<_Tps ...> >::operator T() const [with T = B; Ts = {std::tuple<int, int, int>, std::tuple<int, int, int>}]'
main.cpp:60:27: required from here
main.cpp:35:71: error: could not convert '{tuple_cnv<std::tuple<std::tuple<int, int, int>, std::tuple<int, int, int> > >::p_convert<std::tuple<int, int, int> >((* & std::forward<const std::tuple<int, int, int>&>((* & tuple#0)))), tuple_cnv<std::tuple<std::tuple<int, int, int>, std::tuple<int, int, int> > >::p_convert<std::tuple<int, int, int> >((* & std::forward<const std::tuple<int, int, int>&>((* & tuple#1))))}' from '<brace-enclosed initializer list>' to 'B'
return {p_convert(std::forward<decltype(tuple)>(tuple))...};
2 ответа
Проблема здесь:
template<class... Tss>
static tuple_cnv<Tss...> p_convert(std::tuple<Tss...> const& t)
{ return tuple_cnv<std::tuple<Tss...> >(t); }
Вы сделали это как можно сложнее, чтобы найти ошибки. У вас есть две функции с одинаковыми именами, которые делают разные вещи (p_convert()
это дает вам T
и p_convert()
это обрабатывает рекурсию). Это сбивает с толку.
Вместо этого реализуйте apply
(так как вы на C++14). Тогда используйте apply
:
template <class T>
operator T() const {
return std::apply([](auto const&... elems) -> T {
return {p_convert(elems)...};
}, ref);
}
Проблема была в p_convert
функция возвращала неверное значение. Вместо возврата tuple_cnv<std::tuple<Ts...> >
он вернул tuple_cnv<Ts...>
,
поскольку tuple_cnv<Ts...>
не является недопустимым типом, потому что не было tuple
разрешив не кортежные типы, компилятор заменил этот "неизвестный тип" на int
потому что в старом C, когда переменная не указала тип (в очень старом C переменные могли быть введены без указания явного типа), по умолчанию было int
,
Таким образом, компилятор как-то пытался преобразовать оба внутренних std::tuple<int, int, int>
в int
, который не является недействительным преобразованием.
Хороший вывод ошибки компилятора был показан при записи return B{p_convert(std::forward<decltype(tuple)>(tuple))...}
вместо только списка инициализаторов, который показал полное выражение вместо разрешенных типов.
Это полный код с некоторыми улучшениями:
#include <iostream>
#include <utility>
#include <tuple>
#include <functional>
struct A { int i1, i2, i3; };
struct B { A a1, a2; };
template<class... Ts>
constexpr auto indexes(std::tuple<Ts...> const&)
{ return std::index_sequence_for<Ts...>{}; }
namespace impl {
template<class fun_t, class tuple_t, std::size_t... I>
decltype(auto) tuple_to_args(fun_t&& f, tuple_t&& tuple, std::index_sequence<I...> const&)
{ return f(std::get<I>(std::forward<tuple_t>(tuple))...); }
}
template<class fun_t, class tuple_t>
decltype(auto) tuple_to_args(fun_t&& f, tuple_t&& t)
{ return impl::tuple_to_args(std::forward<fun_t>(f), std::forward<tuple_t>(t), indexes(t)); }
namespace impl {
template<class T>
struct tuple_cnv;
}
template<class T>
auto tuple_cnv(T const& t) { return impl::tuple_cnv<T>(t); }
namespace impl {
template<class tuple_t>
class tuple_cnv
{
std::reference_wrapper<tuple_t const> ref;
public:
explicit tuple_cnv(tuple_t const& t) : ref(t) {}
template<class T>
operator T() const
{
auto inner_f = [](auto&&... elements) -> T {
return {p_convert(std::forward<decltype(elements)>(elements))...};
};
return ::tuple_to_args(inner_f, ref.get());
}
private:
template<class T>
static decltype(auto) p_convert(T&& t) { return std::forward<T>(t); }
template<class... Tss>
static auto p_convert(std::tuple<Tss...> const& t)
{ return ::tuple_cnv(t); }
};
}
using tup = std::tuple<std::tuple<int, int, int>, std::tuple<int, int, int> >;
int main()
{
tup t{{3, 4, 5}, {1, 7, 9}};
B b = tuple_cnv(t);
std::cout << b.a2.i3 << '\n'; // It prints 9
}