const 元组到 const 元组?
const tuple to tuple of consts?
如果我有一个元组成员变量,其中元组中的类型是 class 模板的参数包,我可以使用静态成员函数将函数应用于元组中的每个对象,如下所示:
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 带到非常量元组并调用原始重载,但是,好吧,那么我只是抛弃常量。
令人讨厌的是 apply_to_foos
唯一关心常量的部分是签名。只要尊重 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()
成员函数会自然地工作......我觉得一定有更简单的方法来处理这个问题?
如果不复制包含的项目,您不能将 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::array
和 std::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
左值传递给回调函数,例如不能轻易移动。
我 认为 这是 const_cast
假装你的可变参数是常量的好用法。可能是UB,我不是专家
基本上,将逻辑放在 const 版本中以保证 const 的正确性。然后非 const 版本在从引用中删除 const
后调用 const 版本。
首先,从引用中删除常量(可能是更好的方法):
// badly named but turns `const int &` -> `int &`
template <typename T>
struct remove_const_ref {
using type = std::add_lvalue_reference_t<
std::remove_const_t<std::remove_reference_t<T>>>;
};
template <typename T>
using remove_const_ref_t = typename remove_const_ref<T>::type;
然后,常量版本:
template <size_t I = 0, typename F, typename... Tp>
static void apply_to_foos(const 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);
}
以及非常量版本,它只是强制转换和调用:
template <size_t I = 0, typename F, typename... Tp>
static void apply_to_foos(std::tuple<Tp...> &t, F func) {
apply_to_foos(std::as_const(t), [&func](const auto &v) {
func(const_cast<remove_const_ref_t<decltype(v)>>(v));
});
}
示例:
int main() {
std::tuple thingMut = {1, 2, 3, 4};
const std::tuple thingConst = {1, 2, 3, 4};
apply_to_foos(thingMut, [](auto &v) {
v++;
std::cout << "mut frob: " << v << std::endl;
});
apply_to_foos(thingConst, [](auto &v) {
// v++; error: you cannot assign to a variable that is const
std::cout << "const frob: " << v << std::endl;
});
}
如果我有一个元组成员变量,其中元组中的类型是 class 模板的参数包,我可以使用静态成员函数将函数应用于元组中的每个对象,如下所示:
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 带到非常量元组并调用原始重载,但是,好吧,那么我只是抛弃常量。
令人讨厌的是 apply_to_foos
唯一关心常量的部分是签名。只要尊重 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()
成员函数会自然地工作......我觉得一定有更简单的方法来处理这个问题?
如果不复制包含的项目,您不能将 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::array
和 std::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
左值传递给回调函数,例如不能轻易移动。
我 认为 这是 const_cast
假装你的可变参数是常量的好用法。可能是UB,我不是专家
基本上,将逻辑放在 const 版本中以保证 const 的正确性。然后非 const 版本在从引用中删除 const
后调用 const 版本。
首先,从引用中删除常量(可能是更好的方法):
// badly named but turns `const int &` -> `int &`
template <typename T>
struct remove_const_ref {
using type = std::add_lvalue_reference_t<
std::remove_const_t<std::remove_reference_t<T>>>;
};
template <typename T>
using remove_const_ref_t = typename remove_const_ref<T>::type;
然后,常量版本:
template <size_t I = 0, typename F, typename... Tp>
static void apply_to_foos(const 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);
}
以及非常量版本,它只是强制转换和调用:
template <size_t I = 0, typename F, typename... Tp>
static void apply_to_foos(std::tuple<Tp...> &t, F func) {
apply_to_foos(std::as_const(t), [&func](const auto &v) {
func(const_cast<remove_const_ref_t<decltype(v)>>(v));
});
}
示例:
int main() {
std::tuple thingMut = {1, 2, 3, 4};
const std::tuple thingConst = {1, 2, 3, 4};
apply_to_foos(thingMut, [](auto &v) {
v++;
std::cout << "mut frob: " << v << std::endl;
});
apply_to_foos(thingConst, [](auto &v) {
// v++; error: you cannot assign to a variable that is const
std::cout << "const frob: " << v << std::endl;
});
}