tuple_map应该返回什么?

时间:2018-08-07 13:26:36

标签: c++ c++17 stdtuple

我想实现一个通用的tuple_map函数,该函数需要一个函子和一个std::tuple,将该函子应用于该元组的每个元素,并返回一个std::tuple结果。该实现非常简单,但是出现了一个问题:该函数应返回哪种类型?我的实现使用了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::make_tuple来衰减每个参数的类型(对于值,_v),在情况2中,我们使用std::forward_as_tuple来保留引用({{1} } 以供参考)。两种情况各有利弊。

  1. 悬挂引用。

    _r
  2. 引用元组。

    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
    
  3. 仅移动类型。

    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
    
  4. 引用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&>

    std::make_tuple

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

似乎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 个答案:

答案 0 :(得分:7)

您在问题中突出显示的问题是,在按值返回的函子上使用std::forward_as_tuple会在结果元组中留下右值引用。

使用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