【问题标题】:C++: convert tuple to type TC++:将元组转换为类型 T
【发布时间】:2017-09-12 20:08:21
【问题描述】:

我正在尝试使用(隐式)转换运算符创建一个名为 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 以允许创建初始化树- 在构造对象T 时,通过每个内部tuple_cnv 的(隐式)转换运算符列出。

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

【问题讨论】:

  • Microsoft 的 cl.exe 说:error C2440: 'return': cannot convert from 'initializer list' to 'B' 更具可读性。你用的是什么编译器?
  • 一旦外部对象的构造函数开始运行,就任何类成员的构造而言,它都无法控制。外部对象构造函数需要在适当的参数中获取一个元组,并使用它来构造其类成员。
  • @SamVarshavchik 要做到这一点,构造必须以某种方式禁止所有转换?

标签: c++ c++14 implicit-conversion stdtuple


【解决方案1】:

问题出在这里:

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

【讨论】:

  • std::apply 根据 cppreference 来自 C++17。
  • @Peregring-lk 是的,我知道,这就是我说“实施”的原因。你可以在 C++14 中实现它。
  • @peregrin 当你实现它时,把它放在一个特殊的命名空间中,比如 notstd。那么当你升级的时候就很容易清扫和更换了。
  • @Barry 我已经按照你的建议更改了我的实现,但编译器错误仍然存​​在。
  • @Peregring-lk 我知道问题出在哪里,我只是想让你自己找到它。
【解决方案2】:

问题是p_convert 函数返回了无效值。它没有返回tuple_cnv&lt;std::tuple&lt;Ts...&gt; &gt;,而是返回了tuple_cnv&lt;Ts...&gt;

由于tuple_cnv&lt;Ts...&gt; 不是无效类型,因为没有实例化tuple 允许非元组类型,所以编译器已将该“未知类型”替换为int,因为在旧C 中,当变量没有指定类型(在非常古老的 C 中,可以在没有明确指定类型的情况下引入变量),默认为 int

因此,编译器以某种方式试图将内部 std::tuple&lt;int, int, int&gt; 转换为 int,这不是无效的转换。

编写 return B{p_convert(std::forward&lt;decltype(tuple)&gt;(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
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多