在一个自定义调用中加入多个范围

时间:2022-09-08 22:12:15

The Solution section in gnzlbg's SO question boost::range::join for multiple ranges implies it can join many ranges in one client code call to a custom function variadic template that calls boost::join and boost::make_iterator_range. According to that question, answer, and comments, the prior can join 2 ranges and the latter is needed to ensure the non-const overload of the prior is used. Any containers after the 2nd one are supposedly perfect-forwarded via std::forward. But my client code can only successfully call it with a maximum of 3 arguments. Anything more fails to compile. What’s wrong and how to fix it? And is there any Boost entity now that joins many ranges?

gnzlbg的SO question boost: range::join for multiple ranges中的Solution部分意味着它可以在一个客户端代码调用中加入多个range,调用boost: join和boost::make_iterator_range。根据这个问题、答案和注释,前者可以连接两个范围,后者是确保使用前者的非const过载所必需的。第2个集装箱之后的任何集装箱都应该通过std::forward转发。但是我的客户端代码最多只能调用3个参数。任何东西都不能编译。怎么了?现在是否有任何Boost实体加入多个范围?

I’ve copied and pasted that OP’s implementation, editing it here only for whitespace readability and adding the relevant headers:

我已经复制粘贴了OP的实现,在这里编辑它,只为了空格的可读性,并添加了相关的标题:

#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)...)
    );
}

and added my client code:

并增加了我的客户代码:

#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
}

1 个解决方案

#1


5  

There are two things going on. The first is that the following declarations won’t work as intended:

有两件事正在发生。第一,下列声明不能如预期的那样工作:

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)
    )
);

The crux of the matter is that at spot (1) the second overload of join is not in scope. With three arguments no problem manifests because the Args pack has length 1, so the resulting join(std::forward<Arg0>(arg0)) expansion uses the first overload which is in scope.

问题的关键在于,在spot(1)上,join的第二个重载不在范围内。如果有三个参数,就不会出现问题,因为Args包的长度为1,因此产生的连接(std::forward (Arg0))扩展使用的是作用域中的第一个重载。

With four arguments or more the resulting join(std::forward<Arg0>(arg0), ..., std::forward<ArgN>(argN)) expansion needs the second overload but it is not in scope inside the late return type of the selfsame.

有四个或更多参数的结果连接(std: forward (Arg0),…, std::正向 (ArgN)扩展需要第二个重载,但它不在selfsame的后期返回类型的范围内。

One way to fix that is to turn the set of function templates into a set of member function templates because class scope is more lenient:

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

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 {};

Note that what matters is the class scope, not that we have chosen to use operator() as the name of our member function templates. You can just as well use static member function templates named foo and have a normal function template outside of the class that forwards to it.

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


Now we can expose the second problem in that the implementation is buggy and will only work with odd numbers of arguments! Even join(a, b) won’t work, but that bug could have been hidden previously by ADL (i.e. clients would end up effectively calling boost::join(a, b) which obviously works) (Live On Coliru).

现在,我们可以暴露第二个问题,即实现是错误的,并且只使用奇数个参数!即使是join(a, b)也不能工作,但是这个bug可以在ADL之前被隐藏(例如,客户端最终会有效地调用boost: join(a, b)) (Live On Coliru)。

Let’s rewrite the fold:

让我们重写折叠:

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

住在Coliru

#1


5  

There are two things going on. The first is that the following declarations won’t work as intended:

有两件事正在发生。第一,下列声明不能如预期的那样工作:

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)
    )
);

The crux of the matter is that at spot (1) the second overload of join is not in scope. With three arguments no problem manifests because the Args pack has length 1, so the resulting join(std::forward<Arg0>(arg0)) expansion uses the first overload which is in scope.

问题的关键在于,在spot(1)上,join的第二个重载不在范围内。如果有三个参数,就不会出现问题,因为Args包的长度为1,因此产生的连接(std::forward (Arg0))扩展使用的是作用域中的第一个重载。

With four arguments or more the resulting join(std::forward<Arg0>(arg0), ..., std::forward<ArgN>(argN)) expansion needs the second overload but it is not in scope inside the late return type of the selfsame.

有四个或更多参数的结果连接(std: forward (Arg0),…, std::正向 (ArgN)扩展需要第二个重载,但它不在selfsame的后期返回类型的范围内。

One way to fix that is to turn the set of function templates into a set of member function templates because class scope is more lenient:

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

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 {};

Note that what matters is the class scope, not that we have chosen to use operator() as the name of our member function templates. You can just as well use static member function templates named foo and have a normal function template outside of the class that forwards to it.

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


Now we can expose the second problem in that the implementation is buggy and will only work with odd numbers of arguments! Even join(a, b) won’t work, but that bug could have been hidden previously by ADL (i.e. clients would end up effectively calling boost::join(a, b) which obviously works) (Live On Coliru).

现在,我们可以暴露第二个问题,即实现是错误的,并且只使用奇数个参数!即使是join(a, b)也不能工作,但是这个bug可以在ADL之前被隐藏(例如,客户端最终会有效地调用boost: join(a, b)) (Live On Coliru)。

Let’s rewrite the fold:

让我们重写折叠:

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

住在Coliru