4
\$\begingroup\$

How to improve the functionality, exception safeness and other aspects of the following container adaptors?

Allows to write more generic code (not working for, say, <set>):

#include <utility>
#include <iterator>
#include <type_traits>

template< typename container, bool = std::is_const< std::remove_reference_t< container > >::value >
struct consumable_container;

template< typename container >
struct consumable_container< container, false >
{

    consumable_container(container && _container) noexcept
        : container_(std::forward< container >(_container))
    { ; }

    auto
    begin() noexcept
    {
        return std::make_move_iterator(std::begin(container_));
    }

    auto
    end() noexcept
    {
        return std::make_move_iterator(std::end(container_));
    }

private :

    container container_;

};

template< typename container >
struct consumable_container< container, true >
{

    static_assert(!std::is_rvalue_reference< container >::value);

    consumable_container(container && _container) noexcept
        : container_(std::forward< container >(_container))
    { ; }

    auto
    begin() const noexcept
    {
        return std::cbegin(container_);
    }

    auto
    end() const noexcept
    {
        return std::cend(container_);
    }

private :

    container container_;

};

template< typename container >
consumable_container< container >
move_if_not_const(container && _container) noexcept
{
    return std::forward< container >(_container);
}

// some generic code
#include <list>

template< typename container >
auto
transform_to_list(container && _container) noexcept
{
    static_assert(std::is_reference< container >::value || !std::is_const< container >::value);
    std::list< typename std::remove_reference_t< container >::value_type > list_;
    for (auto && value_ : move_if_not_const(std::forward< container >(_container))) {
        list_.push_back(std::forward< decltype(value_) >(value_));
    }
    return list_;
}

// testing data type
#include <iostream>

struct A
{

    A() { std::cout << __PRETTY_FUNCTION__ << std::endl; }
    ~A() { std::cout << __PRETTY_FUNCTION__ << std::endl; }

    A(A const &) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
    A(A &) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
    A(A &&) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

    A & operator = (A const &) { std::cout << __PRETTY_FUNCTION__ << std::endl; return *this; }
    A & operator = (A &) { std::cout << __PRETTY_FUNCTION__ << std::endl; return *this; }
    A & operator = (A &&) { std::cout << __PRETTY_FUNCTION__ << std::endl; return *this; }

};

// testing
#include <deque>
#include <forward_list>
#include <vector>

#include <cstdlib>

int
main()
{
    {
        std::deque< A > const deque_(1);
        std::list< A > ld_ = transform_to_list(deque_);
    }
    std::cout << std::endl;
    {
        std::forward_list< A > forward_list_;
        forward_list_.push_front(A{});
        std::list< A > ls_ = transform_to_list(forward_list_);
    }
    std::cout << std::endl;
    {
        std::vector< A > vector_;
        vector_.push_back(A{});
        std::list< A > lv_ = transform_to_list(std::move(vector_));
    }
    return EXIT_SUCCESS;
}

Allows to enumerate elements in reverse order:

#include <utility>
#include <iterator>

template< typename container >
struct reversed_container
{

    reversed_container(container && _container) noexcept
        : container_(std::forward< container >(_container))
    { ; }

    auto
    begin() noexcept
    {
        return std::rbegin(container_);
    }

    auto
    end() noexcept
    {
        return std::rend(container_);
    }

private :

    container container_;

};

template< typename container >
reversed_container< container >
reverse(container && _container) noexcept
{
    return std::forward< container >(_container);
}

// testing
#include <iostream>
#include <vector>

#include <cstdlib>

int
main()
{
    std::vector< int > vector_({1, 2, 3, 4, 5});
    // using
    for (int i : reverse(vector_)) {
        std::cout << i << std::endl;
    }
    return EXIT_SUCCESS;
}

Allows to print list of elements with delimiter (without trailing delimiter):

#include <utility>
#include <iterator>

template< typename container >
struct head_container
{

    head_container(container && _container) noexcept
        : container_(std::forward< container >(_container))
    { ; }

    auto
    begin() noexcept
    {
        return std::begin(container_);
    }

    auto
    end() noexcept
    {
        auto last = std::end(container_);
        if (last == std::begin(container_)) {
            return last;
        } else {
            return std::prev(last);
        }
    }

private :

    container container_;

};

template< typename container >
head_container< container >
head(container && _container) noexcept
{
    return std::forward< container >(_container);
}

// testing
#include <iostream>
#include <vector>

#include <cstdlib>

int
main()
{
    std::vector< int > vector_({1, 2, 3, 4, 5});
    // using
    if (!vector_.empty()) {
        for (int i : head(vector_)) {
            std::cout << i << ", ";
        }
        std::cout << vector_.back() << std::endl;
    }
    return EXIT_SUCCESS;
}

Allows to treat first element in special way:

#include <utility>
#include <iterator>

template< typename container >
struct tail_container
{

    tail_container(container && _container) noexcept
        : container_(std::forward< container >(_container))
    { ; }

    auto
    begin() noexcept
    {
        auto first = std::begin(container_);
        if (first == std::end(container_)) {
            return first;
        } else {
            return std::next(first);
        }
    }

    auto
    end() noexcept
    {
        return std::end(container_);
    }

private :

    container container_;

};

template< typename container >
tail_container< container >
tail(container && _container) noexcept
{
    return std::forward< container >(_container);
}

// testing
#include <iostream>
#include <vector>

#include <cstdlib>

int
main()
{
    std::vector< int > vector_({1, 2, 3, 4, 5});
    // using
    if (!vector_.empty()) {
        for (int i : tail(vector_)) {
            vector_.front() += i;
        }
        std::cout << vector_.front() << std::endl;
    }
    return EXIT_SUCCESS;
}
\$\endgroup\$
  • \$\begingroup\$ Can't say I see the utility here. All this is provided by the standard library (just using a different syntax). So it seems you are adding a layer of abstraction that separates the developer from the class without providing any benefits. \$\endgroup\$ – Martin York Dec 4 '14 at 18:38
  • \$\begingroup\$ @LokiAstari What is the different syntax? Did you mean the use of iterators itself? \$\endgroup\$ – Orient Dec 4 '14 at 19:54
  • \$\begingroup\$ transform_to_list() available via constructor and iterators. reverse() is available by directly calling rbegin() and rend()` or using a range adapter from boost. Your head() and tail is better adapted using an iterator adapter see stackoverflow.com/a/1430892/14065 \$\endgroup\$ – Martin York Dec 4 '14 at 21:41
  • \$\begingroup\$ @LokiAstari All that you said looks as much more verbose approach. The purpose of adaptors is to reduce the verbosity of user code. I always can write a plenty of code with iterators, but don't want. \$\endgroup\$ – Orient Dec 5 '14 at 6:07
  • \$\begingroup\$ @LokiAstari Syntactic sugar are the salt of this adaptors. \$\endgroup\$ – Orient Dec 5 '14 at 14:22
2
\$\begingroup\$

The point of the code is to reduce verbosity:

Lets examine that:

std::list< A > ls_ = transform_to_list(forward_list_);

// Using iterators.
std::list< A > ls_(begin(forward_list_), end(forward_list_));

// Don't think its that verbose.
// Not only does it work with list but it also works with every other container.

Reverse is already done:

for (int i : reverse(vector_)) {
    std::cout << i << std::endl;
}

// Or we can use the standard library (or probably future extensions)
// Slightly longer but its properly namespaced so that's understandable.
for (auto i : boost::adaptors::reverse(vector_))
    std::cout << i << std::endl;
}

And my favorite:

if (!vector_.empty()) {
    for (int i : head(vector_)) {
        std::cout << i << ", ";
    }
    std::cout << vector_.back() << std::endl;
}

// Or with a specialized iterator.
// Which definitely seems shorter and less verbose.
std::copy(std::begin(vector_), std::end(vector_), PrefexOutputIterator(std::cout, ","));

Code Review

I don't see anything wrong (or that needs improving) with your code. Its nice and clean (some comments may hep the less experienced read the code). But overall it looks fine.

Goal of Code

I don't agree that the code achieves its goals (reduced verbosity of user code). I also think it makes boilerplate code more likely in user code (and boilerplate is bad as it tends to be cut and paste rather than written).

if (!vector_.empty()) {
    for (int i : head(vector_)) {
        std::cout << i << ", ";
    }
    std::cout << vector_.back() << std::endl;
}

Forcing your user to write a whole section of code, rather than simplifying the task of writing code is where you fail.

I also think you need to understand where C++ has been going over the last 6->8 years. This is towards the use of ranges (with iterators as the underlying model used as the building block for ranges).

\$\endgroup\$
  • \$\begingroup\$ WRT transform_to_list: function steal the container's values, if container is not const-qualified. It is also possible to achieve the same functionality by means of simple overloading (for const &, & and &&: two last should be stealed via std::make_move_iterator). In that case it will be too verbose. \$\endgroup\$ – Orient Dec 5 '14 at 15:48
  • \$\begingroup\$ @Orient: Sorry. I disagree. \$\endgroup\$ – Martin York Dec 5 '14 at 15:50
  • \$\begingroup\$ WRT std::copy(beg, end, std::ostream_iterator(std::cout, ", ")); there is unneeded trailing comma. if (beg != end) { std::copy(beg, std::prev(end), std::ostream_iterator(std::cout, ", ")); if (beg != std::prev(end)) { std::cout << *std::prev(end); } } is the solution, but again: too verbose at my mind. \$\endgroup\$ – Orient Dec 5 '14 at 15:54
  • \$\begingroup\$ Another note is: it is hard to make custom PrefexOutputIterator for specific purposes (not only for output text into stream with delimiters). \$\endgroup\$ – Orient Dec 5 '14 at 15:57
  • \$\begingroup\$ You can't disagree with my intentions regarding my own code :). It is not your prerogative. I can make mistake, but I have my free will. You can't decide something instead of me with respect to my intentions. It is nonsense and absurdity if not. \$\endgroup\$ – Orient Dec 5 '14 at 16:03

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.