skip to Main Content

I ran into a portability issue with std::reduce().

There’s a call to std::reduce() that compiles in Visual Studio, but not with gcc. It’s not clear to me from the cpp documentation if the issue is a bug in gcc, or if Visual Studio is not respecting the spec.

I am using an initial integer value. I want to reduce from a container of pairs, and am using a custom binary operator with signature int (int &&, const std::pair<int, bool> &).

The code given below contains my version of reduce() in the MyImpl namespace. The code provides a switch to use either my version or the std version.

Could someone, please, take a quick look at the source code and let me know what is it that I am doing wrong?

#include <iostream>
#include <algorithm>

#include <numeric>
#include <vector>

namespace MyImpl
{
    template <typename IIter, typename T, typename BinaryOp>
    T reduce(IIter first, IIter last, T val, const BinaryOp &bOp)
    {
        for (; first != last; ++first)
            val = bOp(std::move (val), *first);
    
        return val;
    }    
}

int main ()
{
    std::vector<std::pair<int, bool>> vv {std::pair{1, false}, std::pair{2, true}, std::pair{-1, false}, std::pair{7, true}, std::pair{10, false}, };

#define MY_IMPLEMENTATION
#ifdef MY_IMPLEMENTATION
    // I get the results that I expect using my implementation of reduce
    std::cout << MyImpl::reduce (vv.begin (), vv.end (), 0, [] (auto &&value, const auto &ve)
    {
        return (ve.second ? (value + ve.first) : (value));
    });
#else
    // std's implementation gives an error
    std::cout << std::reduce (vv.begin (), vv.end (), 0, [] (auto &&value, const auto &ve)
    {
        return (ve.second ? (value + ve.first) : (value));
    });
#endif

    return 0;
}

Here’s the error –

By the way, if you click on the "source code" link, you will see the Coliru code.

main.cpp: In instantiation of 'main()::<lambda(auto:16&&, const auto:17&)> [with auto:16 = std::pair<int, bool>&; auto:17 = int]':
/usr/local/include/c++/12.1.0/type_traits:2565:26:   required by substitution of 'template<class _Fn, class ... _Args> static std::__result_of_success<decltype (declval<_Fn>()((declval<_Args>)()...)), std::__invoke_other> std::__result_of_other_impl::_S_test(int) [with _Fn = main()::<lambda(auto:16&&, const auto:17&)>&; _Args = {std::pair<int, bool>&, int&}]'
/usr/local/include/c++/12.1.0/type_traits:2576:55:   required from 'struct std::__result_of_impl<false, false, main()::<lambda(auto:16&&, const auto:17&)>&, std::pair<int, bool>&, int&>'
/usr/local/include/c++/12.1.0/type_traits:3050:12:   recursively required by substitution of 'template<class _Result, class _Ret> struct std::__is_invocable_impl<_Result, _Ret, false, std::__void_t<typename _CTp::type> > [with _Result = std::__invoke_result<main()::<lambda(auto:16&&, const auto:17&)>&, std::pair<int, bool>&, int&>; _Ret = int]'
/usr/local/include/c++/12.1.0/type_traits:3050:12:   required from 'struct std::is_invocable_r<int, main()::<lambda(auto:16&&, const auto:17&)>&, std::pair<int, bool>&, int&>'
/usr/local/include/c++/12.1.0/type_traits:3292:44:   required from 'constexpr const bool std::is_invocable_r_v<int, main()::<lambda(auto:16&&, const auto:17&)>&, std::pair<int, bool>&, int&>'
/usr/local/include/c++/12.1.0/numeric:283:21:   required from 'constexpr _Tp std::reduce(_InputIterator, _InputIterator, _Tp, _BinaryOperation) [with _InputIterator = __gnu_cxx::__normal_iterator<pair<int, bool>*, vector<pair<int, bool> > >; _Tp = int; _BinaryOperation = main()::<lambda(auto:16&&, const auto:17&)>]'
ma

2

Answers


  1. Chosen as BEST ANSWER

    My question has been answered! Thanks!


  2. Per cppreference:

    https://en.cppreference.com/w/cpp/algorithm/reduce

    T must meet the requirements of MoveConstructible. and binary_op(init, *first), binary_op(*first, init), binary_op(init, init), and binary_op(*first, *first) must be convertible to T.

    That means there are 4 possible ways for the binary operator to be called by std::reduce(), but your lambdas are only accounting for one of them: binary_op(init, *first), which works fine for MyImpl::reduce() because that is how it is calling the operator. But that is not guaranteed for std::reduce(), as you can clearly see in the very 1st line of the error message:

    main.cpp: In instantiation of ‘main()::<lambda(auto:16&&, const auto:17&)> [with auto:16 = std::pair<int, bool>&; auto:17 = int]’:

    Your std::reduce() lambda is expecting to be called as binary_op(init, *first), but std::reduce() is actually calling it as binary_op(*first, init) instead.

    So, you may need something more like this instead:

    template<typename T>
    inline constexpr bool is_int_v = std::is_same_v<std::remove_cvref_t<T>, int>;
    
    std::cout << std::reduce (vv.begin (), vv.end (), 0,
        [] (const auto &arg1, const auto &arg2)
        {
            if constexpr (is_int_v<decltype(arg1)>)
            {
                if constexpr (is_int_v<decltype(arg2)>)
                {
                    // binary_op(init, init)
                    return arg1;
                }
                else
                {
                    // binary_op(init, *first)
                    return arg1 + (arg2.second ? arg2.first : 0);
                }
            }
            else if constexpr (is_int_v<decltype(arg2)>)
            {
                // binary_op(*first, init)
                return arg2 + (arg1.second ? arg1.first : 0);
            }
            else
            {
                // binary_op(*first, *first)
                return arg1.second ? arg1.first : 0;
            }
        }
    );
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search