1
\$\begingroup\$

This is a follow-up question for A recursive_sum Template Function Implementation with Unwrap Level in C++. As G. Sliepen's answer mentioned, I am trying to implement a recursive struct for retrieve the unwrapped type in nested containers.

The experimental implementation

  • recursive_unwrap_type struct implementation

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

Full Testing Code

The full testing code:

//  A recursive_unwrap_type Struct 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>

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

template<typename T>
concept is_summable = requires(T x) { x + x; };

//  recursive_depth function implementation
template<typename T>
constexpr std::size_t recursive_depth()
{
    return 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;

//  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()
        }
    }
}

void recursive_unwrap_type_tests_vector()
{
    std::cout << "Play with test_vectors:\n\n";

    auto test_vectors = n_dim_container_generator<std::vector, 5, double>(0.11, 5);
    
    recursive_unwrap_type_t<1, decltype(test_vectors)> test1{};
    std::cout << "typeid.name: " << typeid(test1).name() << "\n"
              << "recursive_depth = " << recursive_depth<decltype(test1)>() << "\n\n";

    recursive_unwrap_type_t<2, decltype(test_vectors)> test2{};
    std::cout << "typeid.name: " << typeid(test2).name() << "\n"
              << "recursive_depth = " << recursive_depth<decltype(test2)>() << "\n\n";

    recursive_unwrap_type_t<3, decltype(test_vectors)> test3{};
    std::cout << "typeid.name: " << typeid(test3).name() << "\n"
              << "recursive_depth = " << recursive_depth<decltype(test3)>() << "\n\n";

    recursive_unwrap_type_t<4, decltype(test_vectors)> test4{};
    std::cout << "typeid.name: " << typeid(test4).name() << "\n"
              << "recursive_depth = " << recursive_depth<decltype(test4)>() << "\n\n";

    recursive_unwrap_type_t<5, decltype(test_vectors)> test5{};
    std::cout << "typeid.name: " << typeid(test5).name() << "\n"
              << "recursive_depth = " << recursive_depth<decltype(test5)>() << "\n\n";

    return;
}

int main()
{
    recursive_unwrap_type_tests_vector();

    return 0;
}

The output of the test code (from clang 16.0.0) above:

Play with test_vectors:

typeid.name: St6vectorIS_IS_IS_IdSaIdEESaIS1_EESaIS3_EESaIS5_EE
recursive_depth = 4

typeid.name: St6vectorIS_IS_IdSaIdEESaIS1_EESaIS3_EE
recursive_depth = 3

typeid.name: St6vectorIS_IdSaIdEESaIS1_EE
recursive_depth = 2

typeid.name: St6vectorIdSaIdEE
recursive_depth = 1

typeid.name: d
recursive_depth = 0

Godbolt link

All suggestions are welcome.

The summary information:

  • Which question it is a follow-up to?

    A recursive_sum Template Function Implementation with Unwrap Level in C++.

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

    I am trying to implement a recursive struct for retrieve the unwrapped type in nested containers.

  • Why a new review is being asked for?

    Please review the recursive_unwrap_type struct implementation and its testing function recursive_unwrap_type_tests.

\$\endgroup\$

0

Your Answer

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