c++11boostvariadic-templatesboost-range

boost::range::combine with repeated argument


I would like to use boost::range::combine as a cartesian power instead as just a product.

So instead of such expression boost::range::combine(myRange, myRange, myRange); write something like myCombine(myRange, 3);.

How it can be implemented?


Solution

  • Implementing this in C++17 or C++14 would be a lot easier and cleaner, but since you tagged this with here's a compliant implementation. Here's a generic way of calling a function object f with the same argument repeated N times.

    First, we need a way of binding the first argument of a generic function object f and then accepting any number of arguments:

    template <typename TF, typename T>
    struct bound
    {
        TF _f;
        T _x;
    
        template <typename TFFwd, typename TFwd>
        bound(TFFwd&& f, TFwd&& x)
            : _f{std::forward<TFFwd>(f)}, _x{std::forward<TFwd>(x)}
        {
        }
    
        template <typename... Ts>
        auto operator()(Ts&&... xs)
            -> decltype(_f(_x, std::forward<Ts>(xs)...))
        {
            return _f(_x, std::forward<Ts>(xs)...);
        }
    };
    
    template <typename TF, typename T>
    auto bind_first(TF&& f, T&& x)
        -> decltype(bound<TF&&, T&&>(std::forward<TF>(f), std::forward<T>(x)))
    {
        return bound<TF&&, T&&>(std::forward<TF>(f), std::forward<T>(x));
    }
    

    Then, we need a recursive helper that will bind an argument x multiple TN times:

    template <std::size_t TN>
    struct helper
    {
        template <typename TF, typename T>
        auto operator()(TF&& f, T&& x)
            -> decltype(helper<TN - 1>{}(bind_first(std::forward<TF>(f), x), x))
        {
            return helper<TN - 1>{}(bind_first(std::forward<TF>(f), x), x);
        }
    };
    
    template <>
    struct helper<0>
    {
        template <typename TF, typename T>
        auto operator()(TF&& f, T&& x)
            -> decltype(f(x))
        {
            return f(x);
        }
    };
    

    Finally, we can provide a nice interface:

    template <std::size_t TN, typename TF, typename T>
    auto call_with_same_arg(TF&& f, T&& x)
        -> decltype(helper<TN - 1>{}(std::forward<TF>(f), std::forward<T>(x)))
    {
        return helper<TN - 1>{}(std::forward<TF>(f), std::forward<T>(x));
    }
    

    Usage:

    int add(int a, int b, int c)
    {
        return a + b + c;   
    }
    
    int main()
    {
        assert(call_with_same_arg<3>(add, 5) == 15);
    }
    

    live wandbox example


    Here's a complete C++17 implementation of the same thing:

    template <std::size_t TN, typename TF, typename T>
    decltype(auto) call_with_same_arg(TF&& f, T&& x)
    {
        if constexpr(TN == 1)
        {
            return f(x);
        }
        else
        {
            return call_with_same_arg<TN - 1>(
                [&](auto&&... xs){ return f(x, std::forward<decltype(xs)>(xs)...); }, x);
        }
    }
    

    live wandbox example


    For completeness, C++14 implementation:

    template <std::size_t TN>
    struct helper
    {
        template <typename TF, typename T>
        decltype(auto) operator()(TF&& f, T&& x)
        {
            return helper<TN - 1>{}(
                [&](auto&&... xs){ return f(x, std::forward<decltype(xs)>(xs)...); }, x);
        }
    };
    
    template <>
    struct helper<0>
    {
        template <typename TF, typename T>
        decltype(auto) operator()(TF&& f, T&& x)
        {
            return f(x);
        }
    };
    
    template <std::size_t TN, typename TF, typename T>
    decltype(auto) call_with_same_arg(TF&& f, T&& x)
    {
        return helper<TN - 1>{}(std::forward<TF>(f), std::forward<T>(x));
    }
    

    live wandbox example