1
\$\begingroup\$

This is a follow-up question for A recursive_reduce_all Template Function Implementation in C++. As G. Sliepen's answer mentioned:

std::is_arithmetic_v<> checks whether the type is basically an integer or a float, nothing else will match.

I am trying to implement an arithmeticable concept which checks whether the operations plus, minus ,multiplies and divides are supported by given type.

The experimental implementation

  • arithmeticable Concept Implementation

    //  arithmeticable concept
    template<class T>
    concept arithmeticable = requires(T input)
    {
        std::plus<>{}(input, input);
        std::minus<>{}(input, input);
        std::multiplies<>{}(input, input);
        std::divides<>{}(input, input);
    };
    

Full Testing Code

The full testing code:

// An arithmeticable Concept Implementation in C++

#include <algorithm>
#include <array>
#include <cassert>
#include <chrono>
#include <complex>
#include <concepts>
#include <deque>
#include <execution>
#include <exception>
#include <functional>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <mutex>
#include <numeric>
#include <optional>
#include <queue>
#include <ranges>
#include <stack>
#include <stdexcept>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>

//  arithmeticable concept
template<class T>
concept arithmeticable = requires(T input)
{
    std::plus<>{}(input, input);
    std::minus<>{}(input, input);
    std::multiplies<>{}(input, input);
    std::divides<>{}(input, input);
};

//  is_reservable concept
template<class T>
concept is_reservable = requires(T input)
{
    input.reserve(1);
};

//  is_sized concept, https://codereview.stackexchange.com/a/283581/231235
template<class T>
concept is_sized = requires(T x)
{
    std::size(x);
};

//  recursive_depth function implementation
template<typename T>
constexpr std::size_t recursive_depth()
{
    return std::size_t{0};
}

template<std::ranges::input_range Range>
constexpr std::size_t recursive_depth()
{
    return recursive_depth<std::ranges::range_value_t<Range>>() + 1;
}

//  recursive_variadic_invoke_result_t implementation
template<std::size_t, typename, typename, typename...>
struct recursive_variadic_invoke_result { };

template<typename F, class...Ts1, template<class...>class Container1, typename... Ts>
struct recursive_variadic_invoke_result<1, F, Container1<Ts1...>, Ts...>
{
    using type = Container1<std::invoke_result_t<F,
        std::ranges::range_value_t<Container1<Ts1...>>,
        std::ranges::range_value_t<Ts>...>>;
};

template<std::size_t unwrap_level, typename F, class...Ts1, template<class...>class Container1, typename... Ts>
requires (  std::ranges::input_range<Container1<Ts1...>> &&
            requires { typename recursive_variadic_invoke_result<
                                    unwrap_level - 1,
                                    F,
                                    std::ranges::range_value_t<Container1<Ts1...>>,
                                    std::ranges::range_value_t<Ts>...>::type; })                //  The rest arguments are ranges
struct recursive_variadic_invoke_result<unwrap_level, F, Container1<Ts1...>, Ts...>
{
    using type = Container1<
        typename recursive_variadic_invoke_result<
        unwrap_level - 1,
        F,
        std::ranges::range_value_t<Container1<Ts1...>>,
        std::ranges::range_value_t<Ts>...
        >::type>;
};

template<std::size_t unwrap_level, typename F, typename T1, typename... Ts>
using recursive_variadic_invoke_result_t = typename recursive_variadic_invoke_result<unwrap_level, F, T1, Ts...>::type;

//  recursive_unwrap_type_t struct implementation
template<std::size_t, typename, typename...>
struct recursive_unwrap_type { };

template<class...Ts1, template<class...>class Container1, typename... Ts>
struct recursive_unwrap_type<1, Container1<Ts1...>, Ts...>
{
    using type = std::ranges::range_value_t<Container1<Ts1...>>;
};

template<std::size_t unwrap_level, class...Ts1, template<class...>class Container1, typename... Ts>
requires (  std::ranges::input_range<Container1<Ts1...>> &&
            requires { typename recursive_unwrap_type<
                                    unwrap_level - 1,
                                    std::ranges::range_value_t<Container1<Ts1...>>,
                                    std::ranges::range_value_t<Ts>...>::type; })                //  The rest arguments are ranges
struct recursive_unwrap_type<unwrap_level, Container1<Ts1...>, Ts...>
{
    using type = typename recursive_unwrap_type<
        unwrap_level - 1,
        std::ranges::range_value_t<Container1<Ts1...>>
        >::type;
};

template<std::size_t unwrap_level, typename T1, typename... Ts>
using recursive_unwrap_type_t = typename recursive_unwrap_type<unwrap_level, T1, Ts...>::type;

//  recursive_array_unwrap_type struct implementation, https://stackoverflow.com/a/76347485/6667035
template<std::size_t, typename>
struct recursive_array_unwrap_type { };

template<template<class, std::size_t> class Container,
              typename T,
              std::size_t N>
struct recursive_array_unwrap_type<1, Container<T, N>>
{
    using type = std::ranges::range_value_t<Container<T, N>>;
};

template<std::size_t unwrap_level, template<class, std::size_t> class Container,
              typename T,
              std::size_t N>
requires (  std::ranges::input_range<Container<T, N>> &&
            requires { typename recursive_array_unwrap_type<
                                    unwrap_level - 1,
                                    std::ranges::range_value_t<Container<T, N>>>::type; })                //  The rest arguments are ranges
struct recursive_array_unwrap_type<unwrap_level, Container<T, N>>
{
    using type = typename recursive_array_unwrap_type<
        unwrap_level - 1,
        std::ranges::range_value_t<Container<T, N>>
        >::type;
};

template<std::size_t unwrap_level, class Container>
using recursive_array_unwrap_type_t = typename recursive_array_unwrap_type<unwrap_level, Container>::type;

//  https://codereview.stackexchange.com/a/253039/231235
template<template<class...> class Container = std::vector, std::size_t dim, class T>
constexpr auto n_dim_container_generator(T input, std::size_t times)
{
    if constexpr (dim == 0)
    {
        return input;
    }
    else
    {
        return Container(times, n_dim_container_generator<Container, dim - 1, T>(input, times));
    }
}

namespace UL                                                //   unwrap_level
{
    template< std::ranges::input_range Container,
              std::copy_constructible F>
    requires (std::ranges::view<Container>&&
              std::is_object_v<F>)
    constexpr auto make_view(const Container& input, const F& f) noexcept
    {
        return std::ranges::transform_view(
                input,
                [&f](const auto&& element) constexpr { return recursive_transform(element, f ); } );
    }

    /* Override make_view to catch dangling references.  A borrowed range is
    * safe from dangling..
    */
    template <std::ranges::input_range T>
    requires (!std::ranges::borrowed_range<T>)
    constexpr std::ranges::dangling make_view(T&&) noexcept
    {
        return std::ranges::dangling();
    }

    //  clone_empty_container template function implementation
    template< std::size_t unwrap_level = 1,
              std::ranges::input_range Container,
              std::copy_constructible F>
    requires (std::ranges::view<Container>&&
              std::is_object_v<F>)
    constexpr auto clone_empty_container(const Container& input, const F& f) noexcept
    {
        const auto view = make_view(input, f);
        recursive_variadic_invoke_result<unwrap_level, F, Container> output(std::span{input});
        return output;
    }
    
    //  recursive_transform template function implementation (the version with unwrap_level template parameter)
    template<   std::size_t unwrap_level = 1,
                class T,
                std::copy_constructible F>
    requires (unwrap_level <= recursive_depth<T>() &&        //  handling incorrect unwrap levels more gracefully, https://codereview.stackexchange.com/a/283563/231235
              std::ranges::view<T>&&
              std::is_object_v<F>)         
    constexpr auto recursive_transform(const T& input, const F& f)
    {
        if constexpr (unwrap_level > 0)
        {
            auto output = clone_empty_container(input, f);
            if constexpr (is_reservable<decltype(output)>&&
                          is_sized<decltype(input)>)
            {
                output.reserve(input.size());
                std::ranges::transform(
                    input,
                    std::ranges::begin(output),
                    [&f](auto&& element) { return recursive_transform<unwrap_level - 1>(element, f); }
                );
            }
            else
            {
                std::ranges::transform(
                    input,
                    std::inserter(output, std::ranges::end(output)),
                    [&f](auto&& element) { return recursive_transform<unwrap_level - 1>(element, f); }
                );
            }
            return output;
        }
        else if constexpr(std::regular_invocable<F, T>)
        {
            return std::invoke(f, input);
        }
        else
        {
            static_assert(!std::regular_invocable<F, T>, "Uninvocable?");
        }
    }

    /* This overload of recursive_transform is to support std::array
    */
    template< std::size_t unwrap_level = 1,
              template<class, std::size_t> class Container,
              typename T,
              std::size_t N,
              typename F >
    requires (std::ranges::input_range<Container<T, N>>)
    constexpr auto recursive_transform(const Container<T, N>& input, const F& f)
    {
        Container<recursive_variadic_invoke_result_t<unwrap_level, F, T>, N> output;

        std::ranges::transform(
                        input,
                        std::ranges::begin(output),
                        [&f](auto&& element){ return recursive_transform<unwrap_level - 1>(element, f); }
                    );

        return output;
    }

    //  recursive_transform function implementation (the version with unwrap_level, without using view)
    template<std::size_t unwrap_level = 1, class T, class F>
    requires (!std::ranges::view<T>)
    constexpr auto recursive_transform(const T& input, const F& f)
    {
        if constexpr (unwrap_level > 0)
        {
            static_assert(unwrap_level <= recursive_depth<T>(),
                "unwrap level higher than recursion depth of input");   //  trying to handle incorrect unwrap levels more gracefully
            recursive_variadic_invoke_result_t<unwrap_level, F, T> output{};
            std::ranges::transform(
                input,                      //  passing a range to std::ranges::transform()
                std::inserter(output, std::ranges::end(output)),
                [&f](auto&& element) { return recursive_transform<unwrap_level - 1>(element, f); }
            );
            return output;
        }
        else
        {
            return std::invoke(f, input);   //   use std::invoke()
        }
    }

    //  recursive_transform implementation (the version with unwrap_level, with execution policy)
    template<std::size_t unwrap_level = 1, class ExPo, class T, class F>
    requires (unwrap_level <= recursive_depth<T>() &&        //  handling incorrect unwrap levels more gracefully, https://codereview.stackexchange.com/a/283563/231235
              std::is_execution_policy_v<std::remove_cvref_t<ExPo>>)
    constexpr auto recursive_transform(ExPo execution_policy, const T& input, const F& f)
    {
        if constexpr (unwrap_level > 0)
        {
            recursive_variadic_invoke_result_t<unwrap_level, F, T> output{};
            output.resize(input.size());
            std::mutex mutex;
            std::transform(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input), std::ranges::begin(output),
                [&](auto&& element)
                {
                    std::lock_guard lock(mutex);
                    return recursive_transform<unwrap_level - 1>(execution_policy, element, f);
                });
            return output;
        }
        else
        {
            return f(input);
        }
    }
}

/*  recursive_reduce_all template function performs operation on input container exhaustively
*/
template<typename T>
constexpr auto recursive_reduce_all(const T& input)
{
    return input;
}

template<std::ranges::input_range T>
requires (arithmeticable<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          recursive_depth<T>() == 1)
constexpr auto recursive_reduce_all(const T& input)
{
    return std::reduce(std::ranges::cbegin(input), std::ranges::cend(input));
}

//  overload for std::array
template<template<class, std::size_t> class Container,
              typename T,
              std::size_t N>
requires (arithmeticable<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>> &&
          recursive_depth<Container<T, N>>() == 1)
constexpr auto recursive_reduce_all(const Container<T, N>& input)
{
    return std::reduce(std::ranges::cbegin(input), std::ranges::cend(input));
}

template<std::ranges::input_range T>
requires (arithmeticable<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::ranges::input_range<recursive_unwrap_type_t<1, T>>)
constexpr auto recursive_reduce_all(const T& input)
{
    auto result = recursive_reduce_all(
        UL::recursive_transform<recursive_depth<T>() - 1>(input, [](auto&& element){ return recursive_reduce_all(element); })
        );
    return result;
}

//  recursive_reduce_all template function with execution policy
template<class ExPo, arithmeticable T>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>>)
constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input)
{
    return input;
}

template<class ExPo, std::ranges::input_range T>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          arithmeticable<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          recursive_depth<T>() == 1)
constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input)
{
    return std::reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input));
}

//  recursive_reduce_all template function with execution policy, overload for std::array
template<class ExPo, template<class, std::size_t> class Container,
              typename T,
              std::size_t N>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          arithmeticable<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>> &&
          recursive_depth<Container<T, N>>() == 1)
constexpr auto recursive_reduce_all(ExPo execution_policy, const Container<T, N>& input)
{
    return std::reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input));
}

template<class ExPo, std::ranges::input_range T>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          arithmeticable<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::ranges::input_range<recursive_unwrap_type_t<1, T>>)
constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input)
{
    auto result = recursive_reduce_all(
        UL::recursive_transform<recursive_depth<T>() - 1>(
            execution_policy,
            input,
            [&](auto&& element){ return recursive_reduce_all(execution_policy, element); }
            )
        );
    return result;
}

//  recursive_reduce_all template function with initial value
template<arithmeticable T>
constexpr auto recursive_reduce_all(const T& input1, const T& input2)
{
    return std::plus<>{}(input1, input2);
}

template<std::ranges::input_range T, class TI>
requires (arithmeticable<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI> &&
          recursive_depth<T>() == 1)
constexpr auto recursive_reduce_all(const T& input, TI init)
{
    return std::reduce(std::ranges::cbegin(input), std::ranges::cend(input), init);
}

//  recursive_reduce_all template function with initial value, overload for std::array
template<template<class, std::size_t> class Container,
              typename T,
              std::size_t N,
              class TI>
requires (arithmeticable<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>> &&
          std::same_as<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>, TI> &&
          recursive_depth<Container<T, N>>() == 1)
constexpr auto recursive_reduce_all(const Container<T, N>& input, TI init)
{
    return std::reduce(std::ranges::cbegin(input), std::ranges::cend(input), init);
}

template<std::ranges::input_range T, class TI>
requires (arithmeticable<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::ranges::input_range<recursive_unwrap_type_t<1, T>> &&
          std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI>)
constexpr auto recursive_reduce_all(const T& input, TI init)
{
    auto result = std::plus<>{}(
        init,
        recursive_reduce_all(
            UL::recursive_transform<recursive_depth<T>() - 1>(
                input,
                [&](auto&& element){ return recursive_reduce_all(element); }
                )
            )
        );
    return result;
}

//  recursive_reduce_all template function with execution policy and initial value
template<class ExPo, arithmeticable T>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>>)
constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input1, const T& input2)
{
    return std::plus<>{}(input1, input2);
}

template<class ExPo, std::ranges::input_range T, class TI>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          arithmeticable<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI> &&
          recursive_depth<T>() == 1)
constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input, TI init)
{
    return std::reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input), init);
}

//  recursive_reduce_all template function with execution policy and initial value, overload for std::array
template<class ExPo,
              template<class, std::size_t> class Container,
              typename T,
              std::size_t N,
              class TI>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          arithmeticable<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>> &&
          std::same_as<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>, TI> &&
          recursive_depth<Container<T, N>>() == 1)
constexpr auto recursive_reduce_all(ExPo execution_policy, const Container<T, N>& input, TI init)
{
    return std::reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input), init);
}

template<class ExPo, std::ranges::input_range T, class TI>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          arithmeticable<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::ranges::input_range<recursive_unwrap_type_t<1, T>> &&
          std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI>)
constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input, TI init)
{
    auto result = init + recursive_reduce_all(
        UL::recursive_transform<recursive_depth<T>() - 1>(
            execution_policy,
            input,
            [&](auto&& element){ return recursive_reduce_all(execution_policy, element); })
        );
    return result;
}

//  recursive_reduce_all template function with initial value and specified operation
template<arithmeticable T, class BinaryOp>
requires (std::regular_invocable<BinaryOp, T, T>)
constexpr auto recursive_reduce_all(const T& input1, const T& input2, BinaryOp binary_op)
{
    return std::invoke(binary_op, input1, input2);
}

template<std::ranges::input_range T, class TI, class BinaryOp>
requires (arithmeticable<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI> &&
          recursive_depth<T>() == 1 &&
          std::regular_invocable<
            BinaryOp,
            recursive_unwrap_type_t<recursive_depth<T>(),T>,
            recursive_unwrap_type_t<recursive_depth<T>(), T>>
          )
constexpr auto recursive_reduce_all(const T& input, TI init, BinaryOp binary_op)
{
    return std::reduce(std::ranges::cbegin(input), std::ranges::cend(input), init, binary_op);
}

//  recursive_reduce_all template function with initial value and specified operation, overload for std::array
template<template<class, std::size_t> class Container,
              typename T,
              std::size_t N,
              class TI,
              class BinaryOp>
requires (arithmeticable<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>> &&
          std::same_as<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>, TI> &&
          recursive_depth<Container<T, N>>() == 1 &&
          std::regular_invocable<
            BinaryOp,
            recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>,
            recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>>
          )
constexpr auto recursive_reduce_all(const Container<T, N>& input, TI init, BinaryOp binary_op)
{
    return std::reduce(std::ranges::cbegin(input), std::ranges::cend(input), init, binary_op);
}

template<std::ranges::input_range T, class TI, class BinaryOp>
requires (arithmeticable<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::ranges::input_range<recursive_unwrap_type_t<1, T>> &&
          std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI> &&
          std::regular_invocable<
            BinaryOp,
            recursive_unwrap_type_t<recursive_depth<T>(),T>,
            recursive_unwrap_type_t<recursive_depth<T>(), T>>
          )
constexpr auto recursive_reduce_all(const T& input, TI init, BinaryOp binary_op)
{
    auto result = init + recursive_reduce_all(
        UL::recursive_transform<recursive_depth<T>() - 1>(
            input,
            [&](auto&& element){ return recursive_reduce_all(element, init, binary_op); })
        );
    return result;
}

//  recursive_reduce_all template function with execution policy, initial value and specified operation
template<class ExPo, arithmeticable T, class BinaryOp>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          std::regular_invocable<BinaryOp, T, T>)
constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input1, const T& input2, BinaryOp binary_op)
{
    return std::invoke(binary_op, input1, input2);
}

template<class ExPo, std::ranges::input_range T, class TI, class BinaryOp>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          arithmeticable<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI> &&
          recursive_depth<T>() == 1 &&
          std::regular_invocable<
            BinaryOp,
            recursive_unwrap_type_t<recursive_depth<T>(),T>,
            recursive_unwrap_type_t<recursive_depth<T>(), T>>
          )
constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input, TI init, BinaryOp binary_op)
{
    return std::reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input), init, binary_op);
}

//  recursive_reduce_all template function with execution policy, initial value and specified operation, overload for std::array
template<class ExPo,
              template<class, std::size_t> class Container,
              typename T,
              std::size_t N,
              class TI, class BinaryOp>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          arithmeticable<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>> &&
          std::same_as<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>, TI> &&
          recursive_depth<Container<T, N>>() == 1 &&
          std::regular_invocable<
            BinaryOp,
            recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>,
            recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>>
          )
constexpr auto recursive_reduce_all(ExPo execution_policy, const Container<T, N>& input, TI init, BinaryOp binary_op)
{
    return std::reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input), init, binary_op);
}

template<class ExPo, std::ranges::input_range T, class TI, class BinaryOp>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          arithmeticable<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::ranges::input_range<recursive_unwrap_type_t<1, T>> &&
          std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI> &&
          std::regular_invocable<
            BinaryOp,
            recursive_unwrap_type_t<recursive_depth<T>(),T>,
            recursive_unwrap_type_t<recursive_depth<T>(), T>>
          )
constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input, TI init, BinaryOp binary_op)
{
    auto result = init + recursive_reduce_all(
        UL::recursive_transform<recursive_depth<T>() - 1>(
            execution_policy,
            input,
            [&](auto&& element){ return recursive_reduce_all(execution_policy, element, init, binary_op); })
        );
    return result;
}

template<class T>
requires (std::ranges::input_range<T>)
constexpr auto recursive_print(const T& input, const int level = 0)
{
    T output = input;
    std::cout << std::string(level, ' ') << "Level " << level << ":" << std::endl;
    std::transform(input.cbegin(), input.cend(), output.begin(), 
        [level](auto&& x)
        {
            std::cout << std::string(level, ' ') << x << std::endl;
            return x;
        }
    );
    return output;
}

template<class T>
requires (std::ranges::input_range<T> &&
          std::ranges::input_range<std::ranges::range_value_t<T>>)
constexpr T recursive_print(const T& input, const int level = 0)
{
    T output = input;
    std::cout << std::string(level, ' ') << "Level " << level << ":" << std::endl;
    std::transform(input.cbegin(), input.cend(), output.begin(),
        [level](auto&& element)
        {
            return recursive_print(element, level + 1);
        }
    );
    return output;
}

void recursive_reduce_all_tests_vector()
{
    auto test_vectors = n_dim_container_generator<std::vector, 4, double>(1, 4);
    
    std::cout << "Play with test_vectors:\n\n";

    std::cout << "Pure recursive_reduce_all function test: \n";
    auto recursive_reduce_all_result1 = recursive_reduce_all(test_vectors);
    std::cout << recursive_reduce_all_result1 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::seq): \n";
    auto recursive_reduce_all_result2 = recursive_reduce_all(std::execution::seq, test_vectors);
    std::cout << recursive_reduce_all_result2 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par): \n";
    auto recursive_reduce_all_result3 = recursive_reduce_all(std::execution::par, test_vectors);
    std::cout << recursive_reduce_all_result3 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par_unseq): \n";
    auto recursive_reduce_all_result4 = recursive_reduce_all(std::execution::par_unseq, test_vectors);
    std::cout << recursive_reduce_all_result4 << "\n\n";
    
    std::cout << "recursive_reduce_all function test with initial value: \n";
    auto recursive_reduce_all_result5 = recursive_reduce_all(test_vectors, static_cast<double>(1));
    std::cout << recursive_reduce_all_result5 << "\n\n";
    
    std::cout << "recursive_reduce_all function test with execution policy (std::execution::seq) and initial value: \n";
    auto recursive_reduce_all_result6 = recursive_reduce_all(std::execution::seq, test_vectors, static_cast<double>(1));
    std::cout << recursive_reduce_all_result6 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par) and initial value: \n";
    auto recursive_reduce_all_result7 = recursive_reduce_all(std::execution::par, test_vectors, static_cast<double>(1));
    std::cout << recursive_reduce_all_result7 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par_unseq) and initial value: \n";
    auto recursive_reduce_all_result8 = recursive_reduce_all(std::execution::par_unseq, test_vectors, static_cast<double>(1));
    std::cout << recursive_reduce_all_result8 << "\n\n";
    
    std::cout << "recursive_reduce_all function test with initial value and specified operation: \n";
    auto recursive_reduce_all_result9 = recursive_reduce_all(test_vectors, static_cast<double>(1), [](auto&& input1, auto&& input2) { return input1 * input2; });
    std::cout << recursive_reduce_all_result9 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::seq), initial value and specified operation: \n";
    auto recursive_reduce_all_result10 = recursive_reduce_all(std::execution::seq, test_vectors, static_cast<double>(1), [](auto&& input1, auto&& input2) { return input1 * input2; });
    std::cout << recursive_reduce_all_result10 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par), initial value and specified operation: \n";
    auto recursive_reduce_all_result11 = recursive_reduce_all(std::execution::par, test_vectors, static_cast<double>(1), [](auto&& input1, auto&& input2) { return input1 * input2; });
    std::cout << recursive_reduce_all_result11 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par_unseq), initial value and specified operation: \n";
    auto recursive_reduce_all_result12 = recursive_reduce_all(std::execution::par_unseq, test_vectors, static_cast<double>(1), [](auto&& input1, auto&& input2) { return input1 * input2; });
    std::cout << recursive_reduce_all_result12 << "\n\n";
    
    return;
}

void recursive_reduce_all_tests_array()
{
    auto test_array = std::array<double, 4>{1, 1, 1, 1};

    std::cout << "Play with test_array:\n\n";

    std::cout << "Pure recursive_reduce_all function test: \n";
    auto recursive_reduce_all_result1 = recursive_reduce_all(test_array);
    std::cout << recursive_reduce_all_result1 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::seq): \n";
    auto recursive_reduce_all_result2 = recursive_reduce_all(std::execution::seq, test_array);
    std::cout << recursive_reduce_all_result2 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par): \n";
    auto recursive_reduce_all_result3 = recursive_reduce_all(std::execution::par, test_array);
    std::cout << recursive_reduce_all_result3 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par_unseq): \n";
    auto recursive_reduce_all_result4 = recursive_reduce_all(std::execution::par_unseq, test_array);
    std::cout << recursive_reduce_all_result4 << "\n\n";
     
    std::cout << "recursive_reduce_all function test with initial value: \n";
    auto recursive_reduce_all_result5 = recursive_reduce_all(test_array, static_cast<double>(1));
    std::cout << recursive_reduce_all_result5 << "\n\n";
    
    std::cout << "recursive_reduce_all function test with execution policy (std::execution::seq) and initial value: \n";
    auto recursive_reduce_all_result6 = recursive_reduce_all(std::execution::seq, test_array, static_cast<double>(1));
    std::cout << recursive_reduce_all_result6 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par) and initial value: \n";
    auto recursive_reduce_all_result7 = recursive_reduce_all(std::execution::par, test_array, static_cast<double>(1));
    std::cout << recursive_reduce_all_result7 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par_unseq) and initial value: \n";
    auto recursive_reduce_all_result8 = recursive_reduce_all(std::execution::par_unseq, test_array, static_cast<double>(1));
    std::cout << recursive_reduce_all_result8 << "\n\n";

    std::cout << "recursive_reduce_all function test with initial value and specified operation: \n";
    auto recursive_reduce_all_result9 = recursive_reduce_all(test_array, static_cast<double>(1), [](auto&& input1, auto&& input2) { return input1 * input2; });
    std::cout << recursive_reduce_all_result9 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::seq), initial value and specified operation: \n";
    auto recursive_reduce_all_result10 = recursive_reduce_all(std::execution::seq, test_array, static_cast<double>(1), [](auto&& input1, auto&& input2) { return input1 * input2; });
    std::cout << recursive_reduce_all_result10 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par), initial value and specified operation: \n";
    auto recursive_reduce_all_result11 = recursive_reduce_all(std::execution::par, test_array, static_cast<double>(1), [](auto&& input1, auto&& input2) { return input1 * input2; });
    std::cout << recursive_reduce_all_result11 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par_unseq), initial value and specified operation: \n";
    auto recursive_reduce_all_result12 = recursive_reduce_all(std::execution::par_unseq, test_array, static_cast<double>(1), [](auto&& input1, auto&& input2) { return input1 * input2; });
    std::cout << recursive_reduce_all_result12 << "\n\n";

    return;
}

int main()
{
    auto start = std::chrono::system_clock::now();
    recursive_reduce_all_tests_vector();
    recursive_reduce_all_tests_array();
    auto end = std::chrono::system_clock::now();
    std::chrono::duration<double> elapsed_seconds = end - start;
    std::time_t end_time = std::chrono::system_clock::to_time_t(end);
    std::cout << "Computation finished at " << std::ctime(&end_time) << "elapsed time: " << elapsed_seconds.count() << '\n';
    return 0;
}

The output of the test code above:

Play with test_vectors:

Pure recursive_reduce_all function test: 
256

recursive_reduce_all function test with execution policy (std::execution::seq): 
256

recursive_reduce_all function test with execution policy (std::execution::par): 
256

recursive_reduce_all function test with execution policy (std::execution::par_unseq): 
256

recursive_reduce_all function test with initial value: 
257

recursive_reduce_all function test with execution policy (std::execution::seq) and initial value: 
257

recursive_reduce_all function test with execution policy (std::execution::par) and initial value: 
257

recursive_reduce_all function test with execution policy (std::execution::par_unseq) and initial value: 
257

recursive_reduce_all function test with initial value and specified operation: 
65

recursive_reduce_all function test with execution policy (std::execution::seq), initial value and specified operation: 
65

recursive_reduce_all function test with execution policy (std::execution::par), initial value and specified operation: 
65

recursive_reduce_all function test with execution policy (std::execution::par_unseq), initial value and specified operation: 
65

Play with test_array:

Pure recursive_reduce_all function test: 
4

recursive_reduce_all function test with execution policy (std::execution::seq): 
4

recursive_reduce_all function test with execution policy (std::execution::par): 
4

recursive_reduce_all function test with execution policy (std::execution::par_unseq): 
4

recursive_reduce_all function test with initial value: 
5

recursive_reduce_all function test with execution policy (std::execution::seq) and initial value: 
5

recursive_reduce_all function test with execution policy (std::execution::par) and initial value: 
5

recursive_reduce_all function test with execution policy (std::execution::par_unseq) and initial value: 
5

recursive_reduce_all function test with initial value and specified operation: 
1

recursive_reduce_all function test with execution policy (std::execution::seq), initial value and specified operation: 
1

recursive_reduce_all function test with execution policy (std::execution::par), initial value and specified operation: 
1

recursive_reduce_all function test with execution policy (std::execution::par_unseq), initial value and specified operation: 
1

Computation finished at Sun Jul  9 17:26:04 2023
elapsed time: 0.00140964

Godbolt link is here.

All suggestions are welcome.

The summary information:

  • Which question it is a follow-up to?

    A recursive_reduce_all Template Function Implementation in C++.

  • What changes has been made in the code since last question?

    I am trying to implement an arithmeticable concept in this post.

  • Why a new review is being asked for?

    Please review the revised code and all suggestions are welcome.

\$\endgroup\$
2
  • \$\begingroup\$ Are you actually using stuff from all those include files, or is that a list of all files defined in the C++ standard? \$\endgroup\$ Jul 10, 2023 at 18:34
  • 1
    \$\begingroup\$ You forgot increment, decrement, unary plus, unary minus and modulus. As per the standard, bitwise operators are also arithmetic operators. So maybe a different word to describe the concept is needed. How about algebric? \$\endgroup\$ Jul 10, 2023 at 19:05

2 Answers 2

3
\$\begingroup\$

Naming

The name arithmeticable sounds quite weird. arithmetic or is_arithmetic would be better, but of course the second is already taken and the first is easily confused with is_arithmetic. Maybe has_arithmetic_operations, if you don't mind its verbosity?

It doesn't check the return type of the operators

Your concept would accept any type which has math operators that return something completely different from T, including returning void. At first glance, you might want to check that it returns T or std::convertible_to<T>.

Algebraic structure

What you probably intended with this concept is not to test that some operators exist, but rather that the type T has an algebraic structure. In particular, with addition, subtraction, multiplication and division, you are trying to check if T is similar to a field.

In most of the algebraic structures, the result of a binary operation between two Ts is also a T. However, there are many types that have all the operations, but they work on two different types, and/or return a type that's not the same as the inputs. Consider for example subtracting two std::chrono::time_points: the result is a std::chrono::duration. And you cannot add two time_points, while you can add two durations (resulting in another duration) or add a duration and a time_point (resulting in a time_point). That brings me to:

Is this concept useful for your application?

If the application is recursive_reduce_all, I would say that your concept arithmeticable is not useful; it unnecessarily restricts the types that could be reduced over. In fact, only std::plus<>{} needs to be checked if you don't provide a custom reduction function F, and if the caller does specify a reduction function, you should not use any concept at all.

\$\endgroup\$
2
\$\begingroup\$

The tests appear to do much more than simply test the concept definition. I would prefer a much simpler test suite that can easily direct me to the exact failing case, like this:

// ### Arithmetic types

static_assert(arithmeticable<float>);
static_assert(arithmeticable<long double>);
static_assert(arithmeticable<unsigned>);
static_assert(arithmeticable<long long int>);
static_assert(arithmeticable<wchar_t>); // arguable

#include <complex>
static_assert(arithmeticable<std::complex<double>>);
static_assert(arithmeticable<std::complex<char*>>); // Hmm, not sure about this!

#include <cstdint>
static_assert(arithmeticable<std::intmax_t>);
static_assert(arithmeticable<std::uintptr_t>);

#include <functional>
static_assert(arithmeticable<std::reference_wrapper<float>>);


// ### Non-arithmetic types

static_assert(!arithmeticable<char*>);
static_assert(!arithmeticable<void(*)()>);

#include <any>
static_assert(!arithmeticable<std::any>);

#include <array>
static_assert(!arithmeticable<std::array<float,2>>);

#include <chrono>
static_assert(!arithmeticable<std::chrono::system_clock::duration>);
static_assert(!arithmeticable<std::chrono::system_clock::time_point>);

#include <functional>
static_assert(!arithmeticable<std::reference_wrapper<char*>>);

#include <tuple>
static_assert(!arithmeticable<std::tuple<int,int>>);
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.