tuple_map应该返回什么?

Evg*_*Evg 12 c++ stdtuple c++17

我想实现一个带有仿tuple_map函数的泛型函数,并将仿函数std::tuple应用于此元组的每个元素并返回std::tuple结果.实现非常简单,但问题出现了:这个函数应返回什么类型?我的实现使用了std::make_tuple.然而,在这里 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>>>{});
}
Run Code Online (Sandbox Code Playgroud)

在案例1中,我们使用std::make_tuple每个参数的衰减类型(_v对于值),在案例2中,我们使用std::forward_as_tuple哪个保留引用(_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
    
    Run Code Online (Sandbox Code Playgroud)
  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
    
    Run Code Online (Sandbox Code Playgroud)
  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&>
    
    Run Code Online (Sandbox Code Playgroud)
  4. 参考文献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&>
    
    Run Code Online (Sandbox Code Playgroud)

(可能是我出了点问题或错过了一些重要的事情.)

这似乎make_tuple是要走的路:它不会产生悬空引用,仍然可以强制推断出引用类型.你将如何实施tuple_map(以及与之相关的陷阱)?

Rer*_*ito 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)))...);
}
Run Code Online (Sandbox Code Playgroud)

这样,您可以保留fn调用结果的值类别. Coliru现场演示

  • @Evgeny`tuple(x ...)`相当于`make_tuple(x ...)`,除了`reference_wrapper <T>`保持为`reference_wrapper <T>`并且不会成为`T&` . (4认同)