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;
}
1 Answer 1
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).
-
\$\begingroup\$ WRT
transform_to_list
: function steal the container's values, if container is notconst
-qualified. It is also possible to achieve the same functionality by means of simple overloading (forconst &
,&
and&&
: two last should be stealed viastd::make_move_iterator
). In that case it will be too verbose. \$\endgroup\$Tomilov Anatoliy– Tomilov Anatoliy2014年12月05日 15:48:30 +00:00Commented Dec 5, 2014 at 15:48 -
\$\begingroup\$ @Orient: Sorry. I disagree. \$\endgroup\$Loki Astari– Loki Astari2014年12月05日 15:50:40 +00:00Commented Dec 5, 2014 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\$Tomilov Anatoliy– Tomilov Anatoliy2014年12月05日 15:54:56 +00:00Commented Dec 5, 2014 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\$Tomilov Anatoliy– Tomilov Anatoliy2014年12月05日 15:57:17 +00:00Commented Dec 5, 2014 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\$Tomilov Anatoliy– Tomilov Anatoliy2014年12月05日 16:03:21 +00:00Commented Dec 5, 2014 at 16:03
transform_to_list()
available via constructor and iterators.reverse()
is available by directly callingrbegin() and
rend()` or using arange
adapter from boost. Yourhead()
and tail is better adapted using an iterator adapter see stackoverflow.com/a/1430892/14065 \$\endgroup\$