遍历 std::tuple 并获取分量的值

标准库提供了两个关于 std::tuple 的访问函数:std::getstd::get_if,它们都能用下标或者是类型作为参数,要求标准库帮我们遍历并访问一个 std::tuple 的指定类型分量。

虽然两个函数都能访问 std::tuple 的某个分量,但是它们处理落空情况的逻辑却不同。

std::get 找到了我们期待的成员就返回它的引用,找不到时直接触发编译报错;std::get_if 在找到时会返回指向对应成员的指针,找不到就返回空指针。

无论是哪个版本的函数,在使用类型查找成员分量时,如果这个成员的类型在 std::tuple 中重复多次出现,都会导致程序行为不确定(标准中这叫“程序非良构”)。

假设有这么一个场景:我们有一个包含了许多类型、但绝不重复出现的 std::tuple,我们需要从中拿走某些特定类型的成员分量的值,并且在这个类型不存在时选中我们指定的默认值。

因为已经确保了类型不重复,所以这个场景能够使用 std::get_if 解决;但 std::get_if 返回的是指针,我们需要二次判空再处理,而且指针显得不太好看也不太安全;那么有没有更简洁一点的方案?

有的 bro 有的,我们自己编写一个访问函数就是。

明确一下需求:我们要在一个 std::tuple 中查找指定类型的成员分量,并在找到时取出该成员的值,否则返回我们指定的一个默认值。

这很像 std::optional::value_or,所以我们的函数就叫 value_or

为求简洁,实现这个函数时的遍历策略是取 std::tuple 的下标:

#include <tuple>
#include <type_traits>

template<typename T, std::size_t, typename D, typename Tuple>
constexpr T value_or( Tuple&&, D&&, const std::false_type& );

template<typename T, std::size_t Pos, typename D, typename Tuple>
constexpr typename std::enable_if<std::is_const<typename std::remove_reference<Tuple>::type>::value, T>::type
  value_or( Tuple&& params, D&& fallback, const std::true_type& )
    noexcept( std::is_nothrow_copy_constructible<T>::value )
{
  static_assert( std::is_copy_constructible<T>::value, "T must be copy constructible when Tuple is const" );
  return std::get<Pos>( params );
}
template<typename T, std::size_t Pos, typename D, typename Tuple>
constexpr typename std::enable_if<!std::is_const<typename std::remove_reference<Tuple>::type>::value, T>::type
  value_or( Tuple&& params, D&& fallback, const std::true_type& )
    noexcept( std::is_nothrow_move_constructible<T>::value )
{
  static_assert( std::is_move_constructible<T>::value,
                 "T must be move constructible when Tuple is non-const" );
  return std::move( std::get<Pos>( params ) );
}
template<typename T, std::size_t Pos = 0, typename D, typename Tuple>
constexpr
  typename std::enable_if<( Pos == std::tuple_size<typename std::remove_reference<Tuple>::type>::value ),
                          T>::type
  value_or( Tuple&&, D&& fallback )
    noexcept( std::is_nothrow_move_constructible<T>::value && std::is_nothrow_constructible<T, D>::value )
{
  static_assert( std::is_constructible<T, D>::value,
                 "The type of fallback must be able to be converted to type T" );
  return T( std::forward<D>( fallback ) );
}
template<typename T, std::size_t Pos = 0, typename D, typename Tuple>
constexpr
  typename std::enable_if<( Pos < std::tuple_size<typename std::remove_reference<Tuple>::type>::value ),
                          T>::type
  value_or( Tuple&& params, D&& fallback )
{
  return value_or<T, Pos>(
    std::forward<Tuple>( params ),
    std::forward<D>( fallback ),
    std::is_same<T, typename std::tuple_element<Pos, typename std::decay<Tuple>::type>::type>() );
}
template<typename T, std::size_t Pos, typename D, typename Tuple>
constexpr T value_or( Tuple&& params, D&& fallback, const std::false_type& )
{
  return value_or<T, Pos + 1>( std::forward<Tuple>( params ), std::forward<D>( fallback ) );
}

所有工作不涉及运行时副作用,因此每个函数都可以被标注为 constexpr;编译后会受到常量优化、尾递归优化等优化手段而直接变成访问对应的成员分量,因而是零运行时开销。

因为需求是取出某个分量的值,递归终点处的处理方式就变成了先判断传进来的 std::tuple 是否带有 const 修饰,是就复制一个,否则调用它的移动构造函数。

这个函数可以被修改为传递 lambda 获得默认值的 value_or_else 函数。

代码只需要 C++11,点击这里在线运行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值