6
\$\begingroup\$

This is a follow-up question for A non-nested test_vectors_generator Function for arithmetic_mean Function Testing in C++ and An arithmetic_mean Function For Various Type Arbitrary Nested Iterable Implementation in C++. Besides std::vector test cases generated from test_vectors_generator template function, I am trying to implement std::deque and std::list test cases with test_deques_generator and test_lists_generator functions.

The usage description

Similar to the usage of test_vectors_generator, there are also four parameters in both test_deques_generator function and test_lists_generator function. The first one is a start iteration number of each element, the second one is a end iteration number of each element, the third one is a step size and the fourth one is the element count of each container.

The experimental implementation

The experimental implementation of test_deques_generator and test_lists_generator functions are as below.

namespace ts
{
    //...
    template<class T> requires (!is_iterable<T>)
    constexpr auto test_deques_generator(T start, T end, T step, std::size_t element_count)
    {
        if (element_count == 1)
        {
            std::list<std::deque<T>> output(((end - start) / step) + 1);
            T i = 0;            // incrementor
            std::for_each(output.begin(), output.end(), [&](auto& item) { i+=step; item = std::deque<T>{ i }; });
            return output;
        }
        else
        {
            std::list<std::deque<T>> output{};
            auto test_deques = test_deques_generator(start, end, step, element_count - 1);
            std::for_each(test_deques.begin(), test_deques.end(), [&](const auto item) {
                    for (T i = start; i <= end; i += step)
                    {
                        auto new_element = item;
                        new_element.push_back(i);
                        output.push_back(new_element);
                    }
                });
            return output;
        }
    }

    template<class T> requires (!is_iterable<T>)
    constexpr auto test_lists_generator(T start, T end, T step, std::size_t element_count)
    {
        if (element_count == 1)
        {
            std::list<std::list<T>> output(((end - start) / step) + 1);
            T i = 0;            // incrementor
            std::for_each(output.begin(), output.end(), [&](auto& item) { i+=step; item = std::list<T>{ i }; });
            return output;
        }
        else
        {
            std::list<std::list<T>> output{};
            auto test_deques = test_lists_generator(start, end, step, element_count - 1);
            std::for_each(test_deques.begin(), test_deques.end(), [&](const auto item) {
                    for (T i = start; i <= end; i += step)
                    {
                        auto new_element = item;
                        new_element.push_back(i);
                        output.push_back(new_element);
                    }
                });
            return output;
        }
    }
}

The used is_iterable concept:

template<typename T>
concept is_iterable = requires(T x)
{
    *std::begin(x);
    std::end(x);
};

Test cases

  1. Test cases of test_deques_generator and test_lists_generator template functions

The recursive_print template function (refer to the previous question A recursive_print Function For Various Type Arbitrary Nested Iterable Implementation in C++) is also used here. The output of recursive_print(ts::test_deques_generator(1, 3, 1, 3)); is as follows.

Level 0:
 Level 1:
 1
 1
 1
 Level 1:
 1
 1
 2
 Level 1:
 1
 1
 3
 Level 1:
 1
 2
 1
 Level 1:
 1
 2
 2
 Level 1:
 1
 2
 3
 Level 1:
 1
 3
 1
 Level 1:
 1
 3
 2
 Level 1:
 1
 3
 3
 Level 1:
 2
 1
 1
 Level 1:
 2
 1
 2
 Level 1:
 2
 1
 3
 Level 1:
 2
 2
 1
 Level 1:
 2
 2
 2
 Level 1:
 2
 2
 3
 Level 1:
 2
 3
 1
 Level 1:
 2
 3
 2
 Level 1:
 2
 3
 3
 Level 1:
 3
 1
 1
 Level 1:
 3
 1
 2
 Level 1:
 3
 1
 3
 Level 1:
 3
 2
 1
 Level 1:
 3
 2
 2
 Level 1:
 3
 2
 3
 Level 1:
 3
 3
 1
 Level 1:
 3
 3
 2
 Level 1:
 3
 3
 3

The output of recursive_print(ts::test_lists_generator(1, 3, 1, 3)); is as follows.

Level 0:
 Level 1:
 1
 1
 1
 Level 1:
 1
 1
 2
 Level 1:
 1
 1
 3
 Level 1:
 1
 2
 1
 Level 1:
 1
 2
 2
 Level 1:
 1
 2
 3
 Level 1:
 1
 3
 1
 Level 1:
 1
 3
 2
 Level 1:
 1
 3
 3
 Level 1:
 2
 1
 1
 Level 1:
 2
 1
 2
 Level 1:
 2
 1
 3
 Level 1:
 2
 2
 1
 Level 1:
 2
 2
 2
 Level 1:
 2
 2
 3
 Level 1:
 2
 3
 1
 Level 1:
 2
 3
 2
 Level 1:
 2
 3
 3
 Level 1:
 3
 1
 1
 Level 1:
 3
 1
 2
 Level 1:
 3
 1
 3
 Level 1:
 3
 2
 1
 Level 1:
 3
 2
 2
 Level 1:
 3
 2
 3
 Level 1:
 3
 3
 1
 Level 1:
 3
 3
 2
 Level 1:
 3
 3
 3
  1. Test cases for arithmetic_mean template function

With Boost.Test tool, the std::deque and std::list test cases for arithmetic_mean template function can be implemented as the following code.

BOOST_AUTO_TEST_CASE(test_deques_generator_char)
{
    typedef char TestType;

    TestType start_num = 1;
    TestType end_num = 50;
    TestType step_num = 1;

    for (auto& each_test_vector : ts::test_deques_generator(start_num, end_num, step_num, 3))
    {
        //    Generate expected_value
        double expected_value = 0;
        for (auto& each_item : each_test_vector)
        {
            expected_value += each_item;
        }
        expected_value = expected_value / static_cast<double>(each_test_vector.size());

        BOOST_TEST(expected_value == arithmetic_mean(each_test_vector));
    }
    BOOST_TEST(true);
}

BOOST_AUTO_TEST_CASE(test_deques_generator_int)
{
    typedef int TestType;

    TestType start_num = 1;
    TestType end_num = 50;
    TestType step_num = 1;

    for (auto& each_test_vector : ts::test_deques_generator(start_num, end_num, step_num, 3))
    {
        //    Generate expected_value
        double expected_value = 0;
        for (auto& each_item : each_test_vector)
        {
            expected_value += each_item;
        }
        expected_value = expected_value / static_cast<double>(each_test_vector.size());

        BOOST_TEST(expected_value == arithmetic_mean(each_test_vector));
    }
    BOOST_TEST(true);
}

BOOST_AUTO_TEST_CASE(test_deques_generator_short)
{
    typedef short TestType;

    TestType start_num = 1;
    TestType end_num = 50;
    TestType step_num = 1;

    for (auto& each_test_vector : ts::test_deques_generator(start_num, end_num, step_num, 3))
    {
        //    Generate expected_value
        double expected_value = 0;
        for (auto& each_item : each_test_vector)
        {
            expected_value += each_item;
        }
        expected_value = expected_value / static_cast<double>(each_test_vector.size());

        BOOST_TEST(expected_value == arithmetic_mean(each_test_vector));
    }
    BOOST_TEST(true);
}

BOOST_AUTO_TEST_CASE(test_deques_generator_long)
{
    typedef long TestType;

    TestType start_num = 1;
    TestType end_num = 50;
    TestType step_num = 1;

    for (auto& each_test_vector : ts::test_deques_generator(start_num, end_num, step_num, 3))
    {
        //    Generate expected_value
        double expected_value = 0;
        for (auto& each_item : each_test_vector)
        {
            expected_value += each_item;
        }
        expected_value = expected_value / static_cast<double>(each_test_vector.size());

        BOOST_TEST(expected_value == arithmetic_mean(each_test_vector));
    }
    BOOST_TEST(true);
}

BOOST_AUTO_TEST_CASE(test_deques_generator_long_long_int)
{
    typedef long long int TestType;

    TestType start_num = 1;
    TestType end_num = 50;
    TestType step_num = 1;

    for (auto& each_test_vector : ts::test_deques_generator(start_num, end_num, step_num, 3))
    {
        //    Generate expected_value
        double expected_value = 0;
        for (auto& each_item : each_test_vector)
        {
            expected_value += each_item;
        }
        expected_value = expected_value / static_cast<double>(each_test_vector.size());

        BOOST_TEST(expected_value == arithmetic_mean(each_test_vector));
    }
    BOOST_TEST(true);
}

BOOST_AUTO_TEST_CASE(test_deques_generator_unsigned_char)
{
    typedef unsigned char TestType;

    TestType start_num = 1;
    TestType end_num = 50;
    TestType step_num = 1;

    for (auto& each_test_vector : ts::test_deques_generator(start_num, end_num, step_num, 3))
    {
        //    Generate expected_value
        double expected_value = 0;
        for (auto& each_item : each_test_vector)
        {
            expected_value += each_item;
        }
        expected_value = expected_value / static_cast<double>(each_test_vector.size());

        BOOST_TEST(expected_value == arithmetic_mean(each_test_vector));
    }
    BOOST_TEST(true);
}

BOOST_AUTO_TEST_CASE(test_deques_generator_float)
{
    typedef float TestType;

    TestType start_num = 1;
    TestType end_num = 50;
    TestType step_num = 1;

    for (auto& each_test_vector : ts::test_deques_generator(start_num, end_num, step_num, 3))
    {
        //    Generate expected_value
        double expected_value = 0;
        for (auto& each_item : each_test_vector)
        {
            expected_value += each_item;
        }
        expected_value = expected_value / static_cast<double>(each_test_vector.size());

        BOOST_TEST(expected_value == arithmetic_mean(each_test_vector));
    }
    BOOST_TEST(true);
}

BOOST_AUTO_TEST_CASE(test_deques_generator_double)
{
    typedef double TestType;

    TestType start_num = 1;
    TestType end_num = 50;
    TestType step_num = 1;

    for (auto& each_test_vector : ts::test_deques_generator(start_num, end_num, step_num, 3))
    {
        //    Generate expected_value
        double expected_value = 0;
        for (auto& each_item : each_test_vector)
        {
            expected_value += each_item;
        }
        expected_value = expected_value / static_cast<double>(each_test_vector.size());

        BOOST_TEST(expected_value == arithmetic_mean(each_test_vector));
    }
    BOOST_TEST(true);
}

BOOST_AUTO_TEST_CASE(test_deques_generator_long_double)
{
    typedef long double TestType;

    TestType start_num = 1;
    TestType end_num = 50;
    TestType step_num = 1;

    for (auto& each_test_vector : ts::test_deques_generator(start_num, end_num, step_num, 3))
    {
        //    Generate expected_value
        double expected_value = 0;
        for (auto& each_item : each_test_vector)
        {
            expected_value += each_item;
        }
        expected_value = expected_value / static_cast<double>(each_test_vector.size());

        BOOST_TEST(expected_value == arithmetic_mean(each_test_vector));
    }
    BOOST_TEST(true);
}

BOOST_AUTO_TEST_CASE(test_lists_generator_char)
{
    typedef char TestType;

    TestType start_num = 1;
    TestType end_num = 50;
    TestType step_num = 1;

    for (auto& each_test_vector : ts::test_lists_generator(start_num, end_num, step_num, 3))
    {
        //    Generate expected_value
        double expected_value = 0;
        for (auto& each_item : each_test_vector)
        {
            expected_value += each_item;
        }
        expected_value = expected_value / static_cast<double>(each_test_vector.size());

        BOOST_TEST(expected_value == arithmetic_mean(each_test_vector));
    }
    BOOST_TEST(true);
}

BOOST_AUTO_TEST_CASE(test_lists_generator_int)
{
    typedef int TestType;

    TestType start_num = 1;
    TestType end_num = 50;
    TestType step_num = 1;

    for (auto& each_test_vector : ts::test_lists_generator(start_num, end_num, step_num, 3))
    {
        //    Generate expected_value
        double expected_value = 0;
        for (auto& each_item : each_test_vector)
        {
            expected_value += each_item;
        }
        expected_value = expected_value / static_cast<double>(each_test_vector.size());

        BOOST_TEST(expected_value == arithmetic_mean(each_test_vector));
    }
    BOOST_TEST(true);
}

BOOST_AUTO_TEST_CASE(test_lists_generator_short)
{
    typedef short TestType;

    TestType start_num = 1;
    TestType end_num = 50;
    TestType step_num = 1;

    for (auto& each_test_vector : ts::test_lists_generator(start_num, end_num, step_num, 3))
    {
        //    Generate expected_value
        double expected_value = 0;
        for (auto& each_item : each_test_vector)
        {
            expected_value += each_item;
        }
        expected_value = expected_value / static_cast<double>(each_test_vector.size());

        BOOST_TEST(expected_value == arithmetic_mean(each_test_vector));
    }
    BOOST_TEST(true);
}

BOOST_AUTO_TEST_CASE(test_lists_generator_long)
{
    typedef long TestType;

    TestType start_num = 1;
    TestType end_num = 50;
    TestType step_num = 1;

    for (auto& each_test_vector : ts::test_lists_generator(start_num, end_num, step_num, 3))
    {
        //    Generate expected_value
        double expected_value = 0;
        for (auto& each_item : each_test_vector)
        {
            expected_value += each_item;
        }
        expected_value = expected_value / static_cast<double>(each_test_vector.size());

        BOOST_TEST(expected_value == arithmetic_mean(each_test_vector));
    }
    BOOST_TEST(true);
}

BOOST_AUTO_TEST_CASE(test_lists_generator_long_long_int)
{
    typedef long long int TestType;

    TestType start_num = 1;
    TestType end_num = 50;
    TestType step_num = 1;

    for (auto& each_test_vector : ts::test_lists_generator(start_num, end_num, step_num, 3))
    {
        //    Generate expected_value
        double expected_value = 0;
        for (auto& each_item : each_test_vector)
        {
            expected_value += each_item;
        }
        expected_value = expected_value / static_cast<double>(each_test_vector.size());

        BOOST_TEST(expected_value == arithmetic_mean(each_test_vector));
    }
    BOOST_TEST(true);
}

BOOST_AUTO_TEST_CASE(test_lists_generator_unsigned_char)
{
    typedef unsigned char TestType;

    TestType start_num = 1;
    TestType end_num = 50;
    TestType step_num = 1;

    for (auto& each_test_vector : ts::test_lists_generator(start_num, end_num, step_num, 3))
    {
        //    Generate expected_value
        double expected_value = 0;
        for (auto& each_item : each_test_vector)
        {
            expected_value += each_item;
        }
        expected_value = expected_value / static_cast<double>(each_test_vector.size());

        BOOST_TEST(expected_value == arithmetic_mean(each_test_vector));
    }
    BOOST_TEST(true);
}

BOOST_AUTO_TEST_CASE(test_lists_generator_float)
{
    typedef float TestType;

    TestType start_num = 1;
    TestType end_num = 50;
    TestType step_num = 1;

    for (auto& each_test_vector : ts::test_lists_generator(start_num, end_num, step_num, 3))
    {
        //    Generate expected_value
        double expected_value = 0;
        for (auto& each_item : each_test_vector)
        {
            expected_value += each_item;
        }
        expected_value = expected_value / static_cast<double>(each_test_vector.size());

        BOOST_TEST(expected_value == arithmetic_mean(each_test_vector));
    }
    BOOST_TEST(true);
}

BOOST_AUTO_TEST_CASE(test_lists_generator_double)
{
    typedef double TestType;

    TestType start_num = 1;
    TestType end_num = 50;
    TestType step_num = 1;

    for (auto& each_test_vector : ts::test_lists_generator(start_num, end_num, step_num, 3))
    {
        //    Generate expected_value
        double expected_value = 0;
        for (auto& each_item : each_test_vector)
        {
            expected_value += each_item;
        }
        expected_value = expected_value / static_cast<double>(each_test_vector.size());

        BOOST_TEST(expected_value == arithmetic_mean(each_test_vector));
    }
    BOOST_TEST(true);
}

BOOST_AUTO_TEST_CASE(test_lists_generator_long_double)
{
    typedef long double TestType;

    TestType start_num = 1;
    TestType end_num = 50;
    TestType step_num = 1;

    for (auto& each_test_vector : ts::test_lists_generator(start_num, end_num, step_num, 3))
    {
        //    Generate expected_value
        double expected_value = 0;
        for (auto& each_item : each_test_vector)
        {
            expected_value += each_item;
        }
        expected_value = expected_value / static_cast<double>(each_test_vector.size());

        BOOST_TEST(expected_value == arithmetic_mean(each_test_vector));
    }
    BOOST_TEST(true);
}

A Godbolt link is here.

Note: The compiling output from Godbolt is <Compilation failed> and the error message is Killed - processing time exceeded. This seems to be caused by Boost.Test tool is too large.

All suggestions are welcome.

The summary information:

\$\endgroup\$
5
\$\begingroup\$

Make the desired container type a template parameter

You are duplicating a lot of code, when the only thing that changes is the type of container that your are returning. You should make the container type a template parameter, for example like so:

template<template<class...> class Container = std::vector, class T> requires (!is_iterable<T>)
constexpr auto test_generator(T start, T end, T step, std::size_t element_count)
{
    ...
    std::list<Container<T>> output;
    ...
}

Then you can write:

auto data = test_generator<std::deque>(0, 10, 1, 3);

The template template parameter is not as flexible as you might think though, for example you cannot use a custom container class that is not a template itself, nor can you easily say you want to use a non-standard allocator for the container. You can also do what the STL does for container adapters, like std::queue for example:

template<class T, class Container = std::vector<T>> requires (!is_iterable<T>)
constexpr auto test_generator(T start, T end, T step, std::size_t element_count)
{
    ...
    std::list<Container> output;
    ...
}

Then you have to use it like so:

auto data = test_generator<int, std::deque<int>>(0, 10, 1, 3);

You can also add another requires check for the container type of course.

Consider a non-recursive implementation

Your implementation uses recursion; each recursion builds a list with an element_count of one less than the previous one. It doesn't look very efficient to me; every layer except the outer one builds a list that you discard afterwards. You can instead generate the desired list without using recursion at all, by just writing a cascaded counter (think of how a mechanical tally counter works). Here is a possible implementation:

template<class T, class Container = std::vector<T>>
constexpr auto test_generator(T start, T end, T step, std::size_t element_count)
{
    std::list<Container> output;

    Container counters;
    std::fill_n(std::back_inserter(counters), element_count, start);

    while (true) {
        output.push_back(counters);

        std::size_t n_reset{};
        for (auto &counter: counters) {
            counter += step;

            if (counter <= end)
                break;

            counter = start;
            if (++n_reset == element_count)
                return output;
        }
    }
}

The only difference is that this implementation stores the values in the opposite order as your implementation, but that should not be too hard to change if necessary.

Consider writing a std::generator instead

What if, instead of a std::list<Container<T>>, you would want a std::vector<Container<T>>? What if you want to avoid having to store the whole result before being able to iterate through it? It would be even more flexible if you wrote a real generator function, like:

template<class T, class Container = std::vector<T>> requires(...)
std::generator<Container> test_generator(T start, T end, T step, std::size_t element_count)
{
    Container counters;
    std::fill_n(std::back_inserter(counters), element_count, start);

    while (true) {
        co_yield counters;

        std::size_t n_reset{};
        for (auto &counter: counters) {
            counter += step;

            if (counter <= end)
                break;

            counter = start;
            if (++n_reset == element_count)
                return;
        }
    }
}

And then you could use it like so:

for (auto vec: test_generator(1, 3, 1, 3)) {
    for (auto el: vec)
        std::cout << el << " ";
    std::cout << "\n";
}

You could also make this work for older versions of C++ if you make test_generator a class that has begin() and end() functions that return iterators.

\$\endgroup\$
1
  • \$\begingroup\$ Good review! I wonder if std::ranges::iota_view could be used here. \$\endgroup\$
    – Edward
    Dec 4 '20 at 12:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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