boost::range::join 一次自定义调用中的多个范围

boost::range::join many ranges in one custom call

gnzlbg's SO question boost::range::join for multiple ranges 中的 Solution 部分暗示它可以在一个客户端代码调用中加入多个范围,调用一个自定义函数可变参数模板,该模板调用 boost::joinboost::make_iterator_range。根据该问题、答案和评论,先验可以加入 2 个范围,并且需要后者以确保使用先验的非 const 重载。第二个容器之后的任何容器都应该是 perfect-forwarded via std::forward。但是我的客户端代码最多只能使用 3 个参数成功调用它。任何更多的东西都无法编译。出了什么问题以及如何解决?现在有没有加入许多范围的Boost实体?

我已经复制并粘贴了该 OP 的实现,在此处对其进行编辑只是为了提高空白可读性并添加相关的 headers:

#include <utility>
#include <boost/range/join.hpp>

template<class C>
auto join(C&& c)
-> decltype(boost::make_iterator_range(std::begin(c), std::end(c)))
{
    return boost::make_iterator_range(std::begin(c), std::end(c));
}

template<class C, class D, class... Args>
auto join(C&& c, D&& d, Args&&... args)
-> decltype
(
    boost::join
    (
        boost::join
        (
            boost::make_iterator_range(std::begin(c), std::end(c)),
            boost::make_iterator_range(std::begin(d), std::end(d))
        ),
        join(std::forward<Args>(args)...)
    )
)
{
    return boost::join
    (
        boost::join
        (
            boost::make_iterator_range(std::begin(c), std::end(c)),
            boost::make_iterator_range(std::begin(d), std::end(d))
        ),
        join(std::forward<Args>(args)...)
    );
}

并添加了我的客户端代码:

#include <deque>
#include <array>
#include <vector>
#include <iostream>

int main()
{
    std::deque<int> deq { 0, 1, 2, 3, 4 };
    std::array<int, 4> stl_arr { 5, 6, 7, 8 };
    int c_arr[3] { 9, 10, 11 };
    std::vector<int> vec { 12, 13 };

    for (auto& i : join(deq, stl_arr, c_arr))
    {
        ++i;
        std::cout << i << ", ";         // OK, prints 1 thru 12
    }

    //join(deq, stl_arr, c_arr, vec);   // COMPILER ERROR
}

有两件事正在发生。首先是以下声明不会按预期工作:

template<class C>
auto join(C&& c)
-> decltype(boost::make_iterator_range(std::begin(c), std::end(c)));

template<class C, class D, class... Args>
auto join(C&& c, D&& d, Args&&... args)
-> decltype
(
    boost::join
    (
        boost::join
        (
            boost::make_iterator_range(std::begin(c), std::end(c)),
            boost::make_iterator_range(std::begin(d), std::end(d))
        ),
        join(std::forward<Args>(args)...)
    //  ^^^^-- (1)
    )
);

问题的症结在于点 (1) join 的第二个重载不在范围内。使用三个参数不会出现问题,因为 Args 包的长度为 1,因此生成的 join(std::forward<Arg0>(arg0)) 扩展使用第一个重载, 在范围内是

如果有四个或更多参数,生成的 join(std::forward<Arg0>(arg0), ..., std::forward<ArgN>(argN)) 扩展需要第二次重载,但它不在 selfsame 的后期 return 类型的范围内。

解决这个问题的一种方法是将函数模板集转换为成员函数模板集,因为 class 范围更宽松:

struct join_type {
    template<class C>
    auto operator()(C&& c) const
    -> decltype(boost::make_iterator_range(std::begin(c), std::end(c)))
    {
        return boost::make_iterator_range(std::begin(c), std::end(c));
    }

    template<class C, class D, class... Args>
    auto operator()(C&& c, D&& d, Args&&... args) const
    -> decltype
    (
        boost::join
        (
            boost::join
            (
                boost::make_iterator_range(std::begin(c), std::end(c)),
                boost::make_iterator_range(std::begin(d), std::end(d))
            ),
            (*this)(std::forward<Args>(args)...)
        )
    )
    {
        return boost::join
        (
            boost::join
            (
                boost::make_iterator_range(std::begin(c), std::end(c)),
                boost::make_iterator_range(std::begin(d), std::end(d))
            ),
            (*this)(std::forward<Args>(args)...)
        );
    }
};

constexpr join_type join {};

请注意,重要的是 class 范围,而不是我们选择使用 operator() 作为成员函数模板的名称。您也可以使用名为 foo 的静态成员函数模板,并在转发给它的 class 之外有一个普通函数模板。


现在我们可以揭露第二个问题,因为实现有问题并且只能处理奇数个参数!即使 join(a, b) 也不起作用,但该错误可能已被 ADL 先前隐藏(即客户端最终会有效地调用 boost::join(a, b),这显然有效)(Live On Coliru).

让我们重写折叠:

struct join_type {
    template<class C>
    auto operator()(C&& c) const
    -> decltype(boost::make_iterator_range(begin(c), end(c)))
    {
        return boost::make_iterator_range(begin(c), end(c));
    }

    template<typename First, typename Second, typename... Rest>
    auto operator()(First&& first, Second&& second, Rest&&... rest) const
    -> decltype( (*this)(boost::join(boost::make_iterator_range(begin(first), end(first)), boost::make_iterator_range(begin(second), end(second))), std::forward<Rest>(rest)...) )
    {
        return (*this)(boost::join(boost::make_iterator_range(begin(first), end(first)), boost::make_iterator_range(begin(second), end(second))), std::forward<Rest>(rest)...);
    }
};

constexpr join_type join {};

Live On Coliru