c++c++17c++20backwards-compatibility

C++17 alternative implementation for std::transform_reduce


Im working on a project which was developed using c++20 standard ,but I need to build it on an old embedded linux device which the highest available standard is c++17(gcc 8.3.0). analyzing the code base, I realized the only c++20 feature requirement that forbids the code to be compiled is the lake of the std::transform_reduce that gets the following error:

error: ‘transform_reduce’ is not a member of ‘std’

the referred part of code is:

auto addresses_string = std::transform_reduce(
    localAddresses.begin(), localAddresses.end(), std::string(""),
    [](const std::string& a, const std::string& b) {
      return a + " | " + b;
    },
    [](InetAddress addr) { return addr.str(); });

but as I check the std documentations it seems the c++17 has most of the std::transform_reduce signatures implemented. this project is a CMake based project and it contains the following variables set to use the c++17 standard

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

I was wondering if im doing something wrong or is there an possible alternative implementation available for the missing function in c++17 or how can I implement the same logic without changing the programs high level behavior.


Solution

  • A solution that doesn't sacrifice expressiveness involves writing your transform_reduce as a simple std::accumulate. To do this you need a utility to generate a map_reduce operator:

    template <class M, class R>
    auto map_reduce(M&& map, R&& reduce) {
        return [m = std::forward<M>(map), 
                r = std::forward<R>(reduce)](auto& acc, auto&& val) mutable {
            return r(acc, m(std::forward<decltype(val)>(val)));
        };
    }
    

    your call can then be is written in C++17 as:

    std::accumulate(
        localAddresses.begin(), localAddresses.end(), std::string(""),
        map_reduce(
            [](InetAddress addr) { return addr.str(); },     // transformer    
            [](const std::string& a, const std::string& b) { // accumulator
                return a + " | " + b;
            });
    

    Demo (using numbers converted to strings instead of addresses).

    Reduction (std::accumulate / std::reduce) can pretty much do anything if you figure out how to express your accumulator. To get a sense of how deep the rabbit hole goes, check this presentation.


    A more mundane solution would be to "borrow" the implementation of transform_reduce (say from here) and make it available in a header only if

    #if __cplusplus < 202002L // Using standard older than C++20
      // implementation goes here
    #endif
    

    Of course in a cross platform project, you'd have to account for the combination of language standard and compiler since some library features are implemented out of standard order.