首页 > 解决方案 > const 元组到 const 元组?

问题描述

如果我有一个元组成员变量,其中元组中的类型是类模板的参数包,我可以使用如下静态成员函数将函数应用于元组中的每个对象:

template<size_t I = 0, typename F, typename... Tp>
static void apply_to_foos(std::tuple<Tp...>& t, F func)  {
    auto& foo = std::get<I>(t);
    func(foo);
    if constexpr (I + 1 != sizeof...(Tp))
        apply_to_foos<I + 1>(t, func);
}

例如,如果foos_是元组成员变量,我可以通过以下方式在 foos 上实现一个成员函数:

void frobnicate() {
   apply_to_foos( foos_, [](auto& f){f.frob();} );
}

等等,但是,如果frobnicate是 const 我遇到了foos_现在将是 const 的问题,并且apply_to_foos想要一个非常量元组。所以例如这不起作用(如果 foos 是某种容器)

size_t size() const {
   size_t sz = 0;
   apply_to_foos( foos_, [&sz](auto& f){ sz += f.size();} );
   return sz;
}

我可以实现一个重载,apply_to_foos它需要一个const std::tuple<Tp...>&, const_cast 到一个非常量元组并调用原始重载,但是,那么我只是抛弃了 const-ness。

令人讨厌的是,唯一apply_to_foos关心 const-ness 的部分是签名。只要遵守 const-ness,其他所有东西都可以使用 const 值,例如 lambda 需要引用 const 值等。如果我可以从 const 元组转换为 const 元组,那么我可以实现 const 重载像这样:

template<size_t I = 0, typename F, typename... Tp>
static void apply_to_foos(const std::tuple<Tp...>& t, F func)  {
    auto& tup_of_consts = const_tuple_to_tuple_of_consts(t);
    apply_to_foos(tup_of_consts, func);
}

并且size()成员函数会自然而然地工作......我觉得必须有一种更简单的方法来处理这个问题?

标签: c++tuplesvariadic-templates

解决方案


如果不复制包含的项目,您将无法投射const std::tuple<T1, T2>到。std::tuple<const T1, const T2>

不过,您有两种选择:

1. 推导出问题的类型,t完全回避问题:

template<size_t I = 0, typename F, typename T>
static void apply_to_foos(T& t, F func)  {
    auto& foo = std::get<I>(t);
    func(foo);
    if constexpr (I + 1 != std::tuple_size<T>::value)
        apply_to_foos<I + 1>(t, func);
}

现场演示

使用t推断的类型,这两种情况都会起作用。如果传递了一个const元组,那么T将被推断为const std::tuple<...>创建类型,t const std::tuple<...>&如果传递一个非const元组,那么T将被推断为std::tuple<...>并且类型t将是std::tuple<...>&。唯一需要的其他更改是使用std::tuple_size<T>::value. sizeof...(Tp)请注意,这也允许apply_to_foos使用其他类型,例如std::arraystd::pair

您可能还希望应用完美转发以允许apply_to_foos使用右值并保留元组元素的值类别。例如:

template<size_t I = 0, typename F, typename T>
static void apply_to_foos(T&& t, F func)  {
    func(std::get<I>(std::forward<T>(t)));
    if constexpr (I + 1 != std::tuple_size<std::remove_reference_t<T>>::value)
        apply_to_foos<I + 1>(std::forward<T>(t), func);
}

现场演示

2. 创建一个包含对原始元组内容的引用的新元组

创建第二个重载,apply_to_foos它接受对元组的引用并创建对原始元组元素const的引用的临时元组:const

template<typename F, typename... Tp>
static void apply_to_foos(const std::tuple<Tp...>& t, F func) {
    std::tuple<std::add_const_t<Tp>&...> tuple_of_const = t;
    apply_to_foos(tuple_of_const, func);
}

现场演示

这可以正常工作,但它在价值类别保存方面存在问题。例如,元组右值的元素将作为左值传递给回调函数,const并且不能轻易移动。


推荐阅读