c++ - 控制更复杂的tuple_for_each的多个可变参数包的拆包

标签 c++ templates variadic-templates template-meta-programming sfinae

背景/动机

我一直在玩VC++ 2015,研究一些编写实用程序例程以处理元组和其他可变参数的方式。

我感兴趣的第一个功能是common-or-garden tuple_for_all函数。对于函数f和元组t,依次调用f(get<0>(t)f(get<1>(t)等等。

到目前为止,如此简单。

template<typename Tuple, typename Function, std::size_t... Indices>
constexpr void tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>) {
    using swallow = int[];
    static_cast<void>(swallow{ 0, (std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t))), void(), 0)... });
}

template<typename Function, typename Tuple>
constexpr void tuple_for_each(Function&& f, Tuple&& t) {
    return tuple_for_each_aux(std::forward<Function>(f), std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{});
}

好的,那可以工作(对减少这种情况发生的所有编译和复制/粘贴错误进行模运算)。

但是我的下一个想法是Function在某些情况下可能返回有用/有趣的值,因此我们应该捕获它。天真的,我们可以做这样的事情:
template<typename Tuple, typename Function, std::size_t... Indices>
constexpr auto tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>) {
    return std::make_tuple(std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t)));
}

template<typename Function, typename Tuple>
constexpr auto tuple_for_each(Function&& f, Tuple&& t) {
    return tuple_for_each_aux(std::forward<Function>(f), std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{});
}

这对于函数确实返回值非常有用,但是由于void令人讨厌地退化,并且我们无法生成std::tuple<void>,因此不适用于void返回函数。我们不能通过返回类型直接重载,但是C++使用SFINAE为我们提供了处理此问题的工具:
template<typename Function, typename Tuple, std::size_t... Indices, typename = std::enable_if_t<std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuple>>)>>::value>>
constexpr void tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>) {
    using swallow = int[];
    static_cast<void>(swallow{ 0, (std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t))), void(), 0)... });
}

template<typename Function, typename Tuple, std::size_t... Indices, typename = std::enable_if_t<!std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuple>>)>>::value>>
constexpr decltype(auto) tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>) {
    return std::make_tuple(std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t)))...);
}

template<typename Function, typename Tuple>
constexpr decltype(auto) tuple_for_each(Function&& f, Tuple&& t) {
    return tuple_for_each_aux(std::forward<Function>(f), std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{});
}

这足够好了。如果两者之间的评估顺序一致(void版本是从左到右,返回值的版本取决于编译器,所以可能是从右到左),那就太好了。我们可以通过避开对std::make_tuple的调用并改为准备初始化std::tuple来解决此问题。我不知道是否有比decltype(std::make_tuple(...))更好的东西来构造正确的类型。可能有。
template<typename Function, typename Tuple, std::size_t... Indices, typename = std::enable_if_t<std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuple>>)>>::value>>
constexpr void tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>)
{
    using swallow = int[];
    static_cast<void>(swallow{ 0, (std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t))), void(), 0)... });
}

template<typename Function, typename Tuple, std::size_t... Indices, typename = std::enable_if_t<!std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuple>>)>>::value>>
constexpr decltype(auto) tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>)
{
    return decltype(std::make_tuple(std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t)))...)){std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t))) ...};
}

template<typename Tuple, typename Function>
constexpr decltype(auto) tuple_for_each(Function&& f, Tuple&& t)
{
    return tuple_for_each_aux(std::forward<Function>(f), std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{});
}

(顺便说一句,VC++ 2015现在似乎已被窃听;即使对于大括号的初始化程序也是如此still doesn't use left-to-right evaluation,因为优化器团队doesn't seem to think it's important)

我对std::enable_if_t检查更感兴趣。我们不检查函数是否为元组中的每个类型(仅第一个)返回非void。但实际上,它应该是全有或全无。 Columbo's all_true技术为我们解决了这一问题:
template <bool...> struct bool_pack;

template <bool... v>
using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>;

template<typename Function, typename Tuple, std::size_t... Indices, typename = std::enable_if_t<all_true<std::is_void<std::result_of_t<Function(std::tuple_element_t<Indices, std::decay_t<Tuple>>)>>::value...>::value>>
constexpr void tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>)
{
    using swallow = int[];
    static_cast<void>(swallow{ 0, (std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t))), void(), 0)... });
}

template<typename Function, typename Tuple, std::size_t... Indices, typename = std::enable_if_t<!std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuple>>)>>::value>>
constexpr decltype(auto) tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>)
{
    return decltype(std::make_tuple(std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t)))...)){std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t))) ...};
}

template<typename Function, typename Tuple>
constexpr decltype(auto) tuple_for_each(Function&& f, Tuple&& t)
{
    return tuple_for_each_aux(std::forward<Function>(f), std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{});
}

问题

但是,这有点棘手。虽然tuple_for_each很好用,但我想,如果我加点香料怎么办?带有函数tuple_for_each和元组ft0等并计算t1f(get<0>(t0), get<0>(t1)...)等的f(get<1>(t0), get<1>(t1)...)怎么样?

天真的,我们想做这样的事情:
    using swallow = int[];
    static_cast<void>(swallow{ 0, ((std::forward<Function>(f)(std::get<Indices>(std::forward<Tuples>(ts))...)), void(), 0)... });

天真的,我们希望第一个...扩展Tuples,第二个...扩展Indices。但是参数包扩展不提供这种控制。如果...之前的表达式包含多个参数包,则...尝试并行解压缩所有参数包(VC++;它会发出长度不同的编译器错误),或者根本找不到参数包(g++;它会发出没有包的编译器错误)。

幸运的是,这种情况可以通过附加的间接层来处理,以分离出扩展:
template <bool...> struct bool_pack;

template <bool... v>
using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>;

template<size_t N, typename Function, typename... Tuples, typename = std::enable_if_t<std::is_void<std::result_of_t<Function(std::tuple_element_t<N, std::decay_t<Tuples>>...)>>::value>>
constexpr void tuple_for_each_aux(Function&& f, Tuples&&... ts)
{
    return std::forward<Function>(f)(std::get<N>(std::forward<Tuples>(ts))...);
}

template<typename Function, typename... Tuples, std::size_t... Indices, typename = std::enable_if_t<all_true<std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuples>>...)>>::value>::value>>
constexpr void tuple_for_each_aux(Function&& f, std::index_sequence<Indices...>, Tuples&&... ts)
{
    using swallow = int[];
    static_cast<void>(swallow{ 0, (tuple_for_each_aux<Indices>(std::forward<Function>(f), std::forward<Tuples>(ts)...), void(), 0)... });
}

template<std::size_t N, typename Function, typename... Tuples, typename = std::enable_if_t<!std::is_void<std::result_of_t<Function(std::tuple_element_t<N, std::decay_t<Tuples>>...)>>::value>>
constexpr decltype(auto) tuple_for_each_aux(Function&& f, Tuples&&... ts)
{
    return std::forward<Function>(f)(std::get<N>(std::forward<Tuples>(ts))...);
}

template<typename Function, typename... Tuples, std::size_t... Indices, typename = std::enable_if_t<all_true<!std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuples>>...)>>::value>::value>>
constexpr decltype(auto) tuple_for_each_aux(Function&& f, std::index_sequence<Indices...>, Tuples&&... ts)
{
    return decltype(std::make_tuple(tuple_for_each_aux<Indices>(std::forward<Function>(f), std::forward<Tuples>(ts)...)...)) { tuple_for_each_aux<Indices>(std::forward<Function>(f), std::forward<Tuples>(ts)...)... };
}

template<typename Function, typename Tuple, typename... Tuples>
constexpr decltype(auto) tuple_for_each(Function&& f, Tuple&& t, Tuples&&... ts)
{
    return tuple_for_each_aux(std::forward<Function>(f), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{}, std::forward<Tuple>(t), std::forward<Tuples>(ts)...);
}

效果很好...除了...那些讨厌的enable_ifs。我不得不弱化它们,回到只测试元组中的第一个元素。现在,这不是完全的灾难,因为最里面的扩展可以执行检查。但这不是很好。考虑以下:
struct functor
{
    int operator()(int a, int b) { return a + b; }
    double operator()(double a, double b) { return a + b; }
    void operator()(char, char) { return;  }
};

int main()
{
    auto t1 = std::make_tuple(1, 2.0, 'a');
    auto t2 = std::make_tuple(2, 4.0, 'b');
    tuple_for_each(functor{}, t1, t2);
    return 0;
}
functor对象需要强制使用void路径,因为第三个元组元素上的评估函数返回void。但是我们的启用检查仅关注第一个元素。而且由于失败是在SFINAE驱动的“过载”解决方案之后发生的,因此SFINAE无法将我们保存在这里。

但是同样地,我们不能出于相同的原因而无法双重解压缩enable_if_t表达式,而调用该函数时却无法做到这一点:参数包扩展会感到困惑,并试图同时进行迭代。

这就是我解脱的地方。我在道德上需要一个与调用该函数相同的间接调用,但是我无法立即看到如何编写该间接调用以使其真正起作用。

有什么建议么?

最佳答案

类型的持有人:

template<class...> class typelist {};

别名模板,用于计算将F应用于I中每个元组的Tuples -th个元素的结果:
template<class F, std::size_t I, class...Tuples>
using apply_result_type = decltype(std::declval<F>()(std::get<I>(std::declval<Tuples>())...));

现在计算结果类型列表:
template<class F, std::size_t...Is, class... Tuples>
typelist<apply_result_type<F, Is, Tuples...>...> 
        compute_result_types(typelist<F, Tuples...>, std::index_sequence<Is...>);

template<class F, std::size_t Size, class... Tuples>
using result_types = decltype(compute_result_types(typelist<F, Tuples...>(),
                                                   std::make_index_sequence<Size>()));

并检查类型列表中是否没有void:
template<class... Ts>
all_true<!std::is_void<Ts>::value...> do_is_none_void(typelist<Ts...>);

template<class TL>
using has_no_void_in_list = decltype(do_is_none_void(TL()));

最后是实际的SFINAE(仅显示一个):
template<typename Function, typename... Tuples, std::size_t... Indices,
         typename = std::enable_if_t<!has_no_void_in_list<result_types<Function,
                                                                       sizeof...(Indices),
                                                                       Tuples...>>{}>>
constexpr void tuple_for_each_aux(Function&& f, std::index_sequence<Indices...>, 
                                  Tuples&&... ts)
{
    using swallow = int[];
    static_cast<void>(swallow{ 0, (tuple_for_each_aux<Indices>(std::forward<Function>(f), std::forward<Tuples>(ts)...), void(), 0)... });
}

Demo

关于c++ - 控制更复杂的tuple_for_each的多个可变参数包的拆包,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31780205/

相关文章:

c++ - 如何使用 POCO 解析 xml 文件并将特定节点提取到 std::string?

c++ - 对存储在 std::map 中的值调用方法

c++ - 模板参数推导错误

Javascript MVC - 轻量级?

c++ - 可变参数模板作为模板参数 : deduction works with GCC but not with Clang

c++ - 将 initializer_list 转换为可变参数模板

c++ - 虚拟继承是否强制基类默认可构造?

取决于排序和搜索算法的 C++ 项目

c++ - 这是不确定的吗?

c++ - 当给定默认模板参数时,将选择什么模板值?