我希望实现一个函子和同一个size的一个或多个元组,并将这个函子应用到每个元组的D1的i'th元素中。
示例:
#include
#include
std::tuple t1(1, 2.2, false);
std::tuple t2(3.3, 'a', 888);
std::cout << std::boolalpha;
for_each_in_tuples(
[](auto a1, auto a2)
{
std::cout << a1 << ' ' << a2 << '\n';
}, t1, t2);
// Outputs:
// 1 3.3
// 2.2 a
// false 888执行情况:
#include
#include
#include
namespace impl
{
template
struct First { using Type = T; };
template
using First_t = typename First::Type;
template
inline constexpr auto all_same = (... && (value == values));
template
inline constexpr auto tuple_size = std::tuple_size_v>;
template
constexpr void for_each_in_tuples(Function func, Tuples&&... tuples)
{
constexpr auto size = tuple_size>;
func(std::get(std::forward(tuples))...);
if constexpr (index + 1 < size)
for_each_in_tuples(func, std::forward(tuples)...);
}
}
template
constexpr void for_each_in_tuples(Function func, Tuples&&... tuples)
{
static_assert(sizeof...(Tuples) > 0);
static_assert(impl::all_same...>);
impl::for_each_in_tuples(func, std::forward(tuples)...);
}在编译器资源管理器:https://godbolt.org/g/cYknQT
主要问题:
for_each_in_tuple还是for_each_in_tuples (或者……)?发布于 2018-08-07 08:10:10
template
struct First { using Type = T; };
template
using First_t = typename First::Type;这可以结合使用std::tuple_element和std::tuple来完成。
template
using First_t = std::tuple_element_t<0, std::tuple>;template
inline constexpr auto all_same = (... && (value == values));对于一个空包,您真的希望这个值是未定义的吗?空包的默认行为是&&,true和||被认为是false。
static_assert(sizeof...(Tuples) > 0);而不是失败,也许试着用0参数调用函数,看看会发生什么?
static_assert(impl::all_same...>);如果您想要类似于压缩的行为,您将希望压缩元组,直到其中一个元组耗尽了它的元素(min大小而不是第一个大小)。
这个实现是正确的吗?可以简化吗?
是。使用迭代方法,如顺序展开,超过递归。
哪个名字更好,for_each_in_tuple还是for_each_in_tuples (或者……)?
也许是for_each_zipped。元组可以从函数的签名中收集。
如果要避免递归,请使用折叠表达式、std::index_sequence和std::make_index_sequence。
从一个简单的助手开始,只调用函数,在特定索引处跨所有元组调用元素。
template
constexpr void invoke_at(Function&& func, Tuples&&... tuples) {
func(std::get(std::forward(tuples))...);
}现在我们需要一种顺序调用它的方法(invoke_at<0>(args), invoke_at<1>(args), ..., invoke(args))。使用折叠表达式,就像对all_same一样,但使用逗号运算符和一元右折叠((invoke_at(args), ...))。为了生成扩展的Ns,我们使用std::index_sequence。
template
constexpr void apply_sequence(Function&& func, std::index_sequence, Tuples&&... tuples) {
(((void)invoke_at(std::forward(func), std::forward(tuples)...), ...));
}最后,编写函数,检查先决条件,创建索引序列,并将参数转发给上面的助手。
template
constexpr void tuple_for_each(Function&& func, Tuples&&... tuples) {
static_assert(sizeof...(tuples) > 0, "Must be called with at least one tuple argument");
constexpr auto min_length = std::min({std::tuple_size_v>...});
if constexpr (min_length != 0) {
impl::apply_sequence(std::forward(func),
std::make_index_sequence{},
std::forward(tuples)...);
}
else {
func();
}
}注意-该扩展有一个强制转换为void,它禁用任何滥用逗号运算符的重载的恶作剧。
发布于 2018-08-06 20:40:40
关于您的代码,我没什么可批评的;据我所见,每件事似乎都经过深思熟虑,而且写得很好。
关于问题1:是的,我认为你的实施是正确的。我也不认为有什么需要简化的;实现非常简洁,在得到constexpr while或constexpr for (如果有的话)之前,消除递归是不可能的。
至于问题2,我非常赞成for_each_in_tuples,因为您显然是一次迭代多个元组。
如果有什么需要改进的地方,我会说是First。因为参数包是“类似”的类型列表,所以在我看来,Head这个名字更适合。此外,与正常函数参数一样,我认为保留未使用的参数不命名也是很好的方式。
template
struct Head { using Type = T; };不过,这确实只是个人偏好。
但是,您的代码至少有一个真正的“问题”,这与for_each_in_tuples中的D7参数有关:由于您通过值传递func,如果func不是一个轻量级函数对象(例如,lambda捕获了一些具有昂贵的复制操作的对象),则可能会导致性能下降。此外,这种按值传递的语义还会使用户无法使用根本不支持复制操作的函数对象,例如,lambda捕获任何仅移动类型的内容。为了解决这个问题,我建议您只在每个递归调用中使用std::move func,这将涵盖几乎所有的情况(除了昂贵的移动类型之外,但它们非常罕见)。
发布于 2018-08-07 09:35:34
您的实现是正确的,即使Snowhawk很好地指出了可以改进的地方(尽管我认为递归方法并不比“顺序扩展”更复杂)。
我主要关注的是更一般的设计: 1) zip、tuple和2)在压缩元素上应用函数做了一件事太多了。它应该由两个函数组成:一个将函数应用到一个元组的成员(比如tuple_for_each)和一个压缩元组(比如tuples_zip)。这意味着您也可以在不需要压缩的情况下使用tuple_for_each,在不需要向压缩元素应用函数时也可以使用元组。
此外,for_each也失去了通用性,因为当您需要恢复正在应用的函数的结果时(可以使用lambda捕获,但这是一个扭曲),因此不能使用它。因此,我主张使用一个tuple_map函数,返回包含应用程序结果的元组(在有应用程序的情况下)。
tuple_map应该是这样的:
#include
#include
template
constexpr auto tuple_map_impl(Fn&& fn, std::index_sequence, Tuple&& tuple) {
// if fn's return type is void, do not return a tuple since it'd be invalid
using fn_return_type = decltype(fn(std::get<0>(tuple)));
if constexpr (std::is_same_v)
(std::forward(fn)(std::get(std::forward(tuple))),...);
else
return std::forward_as_tuple(std::forward(fn)(std::get(std::forward(tuple)))...);
}
template
constexpr auto tuple_map(Fn&& fn, Tuple&& tuple) {
constexpr auto tsz = std::tuple_size_v>;
// we need to handle the case where tuple size == 0 because we check fn(std::get<0>(tuple)) in tuple_map_impl
if constexpr (tsz == 0) return std::forward(tuple);
else return tuple_map_impl(std::forward(fn),
std::make_index_sequence>>(),
std::forward(tuple));
}tuple_zip是这样的:
template
auto zip_tuples_at(Tuples&&... tuples) {
return std::forward_as_tuple(std::get(std::forward(tuples))...);
}
template
constexpr auto indexes() {
return std::make_index_sequence>>();
}
template
constexpr auto tuples_zip_impl(std::index_sequence, Tuples&&... tuples) {
return std::make_tuple(zip_tuples_at(tuples...)...);
}
template
constexpr auto tuples_zip(Tuples&&... tuples) {
return tuples_zip_impl(indexes(), std::forward(tuples)...);
}然后,我们可以用这两个函数和zipped_tuples_map组成std::apply(我对函数名称的建议):
template
auto zipped_tuples_map(Fn&& fn, Tuples&&... tuples) {
return tuple_map([&fn](auto&& zipped_elements) { return std::apply(std::forward(fn), zipped_elements); },
tuples_zip(std::forward(tuples)...));
} 指向完整示例的链接:魔杖盒。
https://codereview.stackexchange.com/questions/201106
复制相似问题