代码之家  ›  专栏  ›  技术社区  ›  Evg

元组映射应该返回什么?

  •  12
  • Evg  · 技术社区  · 6 年前

    我想实现一个通用的 tuple_map 接受函子和 std::tuple ,将函子应用于此元组的每个元素,并返回 std::元组 结果。实现非常简单,但是问题是:这个函数应该返回什么类型?我使用的实现 std::make_tuple . 然而, here std::forward_as_tuple 有人建议。

    更具体地说,实现(为简洁起见,省略了对空元组的处理):

    #include <cstddef>
    #include <tuple>
    #include <type_traits>
    #include <utility>
    
    template<class Fn, class Tuple, std::size_t... indices>
    constexpr auto tuple_map_v(Fn fn, Tuple&& tuple, std::index_sequence<indices...>)
    {
        return std::make_tuple(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);
        //          ^^^
    }
    
    template<class Fn, class Tuple, std::size_t... indices>
    constexpr auto tuple_map_r(Fn fn, Tuple&& tuple, std::index_sequence<indices...>)
    {
        return std::forward_as_tuple(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);
        //          ^^^
    }
    
    template<class Tuple, class Fn>
    constexpr auto tuple_map_v(Fn fn, Tuple&& tuple)
    { 
        return tuple_map_v(fn, std::forward<Tuple>(tuple), 
            std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
    }
    
    template<class Tuple, class Fn>
    constexpr auto tuple_map_r(Fn fn, Tuple&& tuple)
    { 
        return tuple_map_r(fn, std::forward<Tuple>(tuple), 
            std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
    }
    

    在案例1中,我们使用 std::创建元组 会破坏每一个论点的类型( _v 在案例2中,我们使用 std::转发 保留参考文献( _r 供参考)。这两种情况各有利弊。

    1. 悬垂的参考。

      auto copy = [](auto x) { return x; };
      auto const_id = [](const auto& x) -> decltype(auto) { return x; };
      
      auto r1 = tuple_map_v(copy, std::make_tuple(1));
      // OK, type of r1 is std::tuple<int>
      
      auto r2 = tuple_map_r(copy, std::make_tuple(1));
      // UB, type of r2 is std::tuple<int&&>
      
      std::tuple<int> r3 = tuple_map_r(copy, std::make_tuple(1));
      // Still UB
      
      std::tuple<int> r4 = tuple_map_r(const_id, std::make_tuple(1));
      // OK now
      
    2. 引用元组。

      auto id = [](auto& x) -> decltype(auto) { return x; };
      
      int a = 0, b = 0;
      auto r1 = tuple_map_v(id, std::forward_as_tuple(a, b));
      // Type of r1 is std::tuple<int, int>
      ++std::get<0>(r1);
      // Increments a copy, a is still zero
      
      auto r2 = tuple_map_r(id, std::forward_as_tuple(a, b));
      // Type of r2 is std::tuple<int&, int&>
      ++std::get<0>(r2);
      // OK, now a = 1
      
    3. 仅移动类型。

      NonCopyable nc;
      auto r1 = tuple_map_v(id, std::forward_as_tuple(nc));
      // Does not compile without a copy constructor 
      
      auto r2 = tuple_map_r(id, std::forward_as_tuple(nc));
      // OK, type of r2 is std::tuple<NonCopyable&>
      
    4. 参考文献 std::创建元组 .

      auto id_ref = [](auto& x) { return std::reference_wrapper(x); };
      
      NonCopyable nc;
      auto r1 = tuple_map_v(id_ref, std::forward_as_tuple(nc));
      // OK now, type of r1 is std::tuple<NonCopyable&>
      
      auto r2 = tuple_map_v(id_ref, std::forward_as_tuple(a, b));
      // OK, type of r2 is std::tuple<int&, int&>
      

    (可能是我做错了什么,或者错过了重要的事情。)

    看来 make_tuple 方法是:它不产生悬挂引用,仍然可以强制推断引用类型。你将如何实施 元组映射 (与之相关的陷阱是什么?)?

    1 回复  |  直到 6 年前
        1
  •  7
  •   Rerito    6 年前

    你在问题中强调的问题是 std::forward_as_tuple 在按值返回的函子上,将在结果元组中留下一个rvalue引用。

    通过使用 make_tuple 但是,不能使用 forward_as_tuple ,不能保留纯值。你可以依靠 std::invoke_result 要找出结果元组必须包含的类型,并使用适当的 std::tuple 构造器。

    template<class Fn, class Tuple, std::size_t... indices>
    constexpr auto tuple_map_r(Fn fn, Tuple&& tuple, std::index_sequence<indices...>) {
        using tuple_type = std::tuple<
            typename std::invoke_result<
                Fn, decltype(std::get<indices>(std::forward<Tuple>(tuple)))
            >::type...
        >;
        return tuple_type(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);
    }
    

    这样就可以保留 fn 打电话来。 Live demo on Coliru