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))...};

person Peregring-lk    schedule 12.09.2017    source источник
comment
Microsoft cl.exe говорит: error C2440: 'return': cannot convert from 'initializer list' to 'B' что значительно более читабельно. Какой компилятор вы используете?   -  person Axalo    schedule 12.09.2017
comment
Как только конструктор для внешнего объекта заработает, он выйдет из-под ваших рук, поскольку идет строительство любого члена класса. Конструктор внешнего объекта должен принять кортеж в соответствующем параметре и использовать его для создания своего члена класса.   -  person Sam Varshavchik    schedule 12.09.2017
comment
@SamVarshavchik Чтобы это было правдой, конструкция каким-то образом должна запретить все преобразования?   -  person Barry    schedule 13.09.2017


Ответы (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);
}
person Barry    schedule 12.09.2017
comment
std::apply взят из C++17 согласно cppreference. - person Peregring-lk; 12.09.2017
comment
@ Peregring-lk Да, я знаю, поэтому я сказал реализовать. Вы можете реализовать его на С++ 14. - person Barry; 12.09.2017
comment
@peregrin, когда вы его реализуете, поместите его в специальное пространство имен, например notstd. Тогда при обновлении его легко подмести и заменить. - person Yakk - Adam Nevraumont; 13.09.2017
comment
@Barry Я изменил свою реализацию по вашему предложению, но ошибка компилятора все еще остается. - person Peregring-lk; 13.09.2017
comment
@Barry Да, я удалил рекурсивную функцию: coliru.stacked-crooked.com/a/d732a06db08529fa - person Peregring-lk; 13.09.2017
comment
@ Peregring-lk Нет, проблема все еще существует. - person Barry; 13.09.2017
comment
@Barry, но рекурсия должна быть где-то еще, потому что мне нужно, чтобы каждый внутренний кортеж был преобразован в tuple_cnv, чтобы разрешить рекурсивное преобразование. - person Peregring-lk; 13.09.2017
comment
@ Peregring-lk Я не знаю, что это значит, но часть моего ответа, в которой говорится, что проблема здесь, показывает вам, в чем ваша проблема. Эта функция сломана. - person Barry; 13.09.2017
comment
@Barry Решено и опубликовано как ответ, если вам интересно. - person Peregring-lk; 13.09.2017
comment
@Peregring-lk Я знаю, в чем проблема, я просто хотел, чтобы ты нашел ее сам. - person Barry; 13.09.2017

Проблема заключалась в том, что функция 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
}
person Peregring-lk    schedule 13.09.2017