#include <iostream>
#include <iterator>
#include <sstream>
#include <utility>
// ============================================== Rudimentary TDD-Support
#define SHOW_(expect, ...)\
do {\
std::ostringstream result;\
result.copyfmt(std::cout);\
result << (__VA_ARGS__);\
const bool ok = (result.str() == expect);\
std::cout << (ok ? '*' : '!') << ' '\
<< __FUNCTION__ << ':' << __LINE__ << '\t'\
<< #__VA_ARGS__ << " --> " << result.str();\
if (!ok) {\
std::cout << " != " << expect;\
}\
std::cout << std::endl;\
}\
while (0)
// =======================================================================
#include "my_static_vector.h"
void default_ctor_test() {
my::static_vector<int, 10> obj1{};
SHOW_("48", sizeof obj1);
my::static_vector<double, 3> obj2{};
SHOW_("32", sizeof obj2);
}
void list_ctor_test() {
my::static_vector<int, 5> obj1{5, 9, 12};
SHOW_("5", obj1.at(0));
SHOW_("9", obj1.at(1));
SHOW_("12", obj1.at(2));
}
#if 0
void copy_ctor_test() {
my::static_vector<int, 5> obj1{5, 9, 12};
my::static_vector<int, 5> obj2{obj1};
SHOW_("5", obj1.at(0));
SHOW_("9", obj1.at(1));
SHOW_("12", obj1.at(2));
SHOW_("5", obj2.at(0));
SHOW_("9", obj2.at(1));
SHOW_("12", obj2.at(2));
}
#endif
#if 0
void move_ctor_test() {
my::static_vector<int, 5> obj1{5, 9, 12};
my::static_vector<int, 5> obj2{std::move(obj1)};
SHOW_("5", obj2.at(0));
SHOW_("9", obj2.at(1));
SHOW_("12", obj2.at(2));
}
#endif
void max_size_test() {
my::static_vector<int, 10> obj1;
my::static_vector<double, 3> obj2;
SHOW_("10", obj1.max_size());
SHOW_("3", obj2.max_size());
}
void size_empty_test() {
my::static_vector<int, 10> obj1{};
SHOW_("true", obj1.empty());
SHOW_("0", obj1.size());
obj1.push_back(123);
SHOW_("false", obj1.empty());
SHOW_("1", obj1.size());
obj1.push_back(-1);
SHOW_("false", obj1.empty());
SHOW_("2", obj1.size());
obj1.pop_back();
SHOW_("false", obj1.empty());
SHOW_("1", obj1.size());
obj1.pop_back();
SHOW_("true", obj1.empty());
SHOW_("0", obj1.size());
}
void data_size_test() {
using mv = my::static_vector<int, 10>;
mv obj1{5, 9, 12};
auto data_test =
[](mv::value_type const* d, mv::size_type s) {
std::ostringstream os{};
for (;s; --s) {
os << *(d++) << ' ';
}
return os.str();
};
SHOW_("5 9 12 ", data_test(obj1.data(), obj1.size()));
}
void fast_indexing_test() {
my::static_vector<int, 5> obj1{5, 9, 12};
SHOW_("5", obj1[0]);
SHOW_("9", obj1[1]);
SHOW_("12", obj1[2]);
}
void safe_indexing_test() {
my::static_vector<int, 5> obj1{5, 9, 12};
SHOW_("5", obj1.at(0));
SHOW_("9", obj1.at(1));
SHOW_("12", obj1.at(2));
SHOW_("5", obj1.at(3));
}
void resize_clear_test() {
my::static_vector<int, 5> obj1{5, 9, 12};
SHOW_("3", obj1.size());
SHOW_("5", obj1.at(0));
SHOW_("9", obj1.at(1));
SHOW_("12", obj1.at(2));
obj1.resize(2);
SHOW_("2", obj1.size());
SHOW_("5", obj1.at(0));
SHOW_("9", obj1.at(1));
obj1.resize(4);
SHOW_("4", obj1.size());
SHOW_("5", obj1.at(0));
SHOW_("9", obj1.at(1));
SHOW_("0", obj1.at(2));
SHOW_("0", obj1.at(3));
obj1.clear();
SHOW_("0", obj1.size());
}
void push_pop_back_test() {
my::static_vector<int, 10> obj1{};
obj1.push_back(123);
SHOW_("123", obj1.back());
obj1.push_back(-1);
SHOW_("-1", obj1.back());
obj1.pop_back();
SHOW_("123", obj1.back());
}
#include <string>
class NDC {
std::string s_;
int x_;
public:
NDC(std::string s, int x) : s_{s}, x_{x} {}
std::string to_string() const {
return s_ + " " + std::to_string(x_);
}
};
void emplace_test() {
my::static_vector<NDC, 10> obj1{};
SHOW_("0", obj1.size());
obj1.emplace_back("hello", 42);
SHOW_("1", obj1.size());
SHOW_("hello 42", obj1.back().to_string());
obj1.emplace_back("whatever", 0);
SHOW_("2", obj1.size());
SHOW_("whatever 0", obj1.back().to_string());
}
void front_back_test() {
my::static_vector<int, 10> obj1{};
obj1.push_back(123);
SHOW_("123", obj1.front());
SHOW_("123", obj1.back());
obj1.push_back(-1);
SHOW_("123", obj1.front());
SHOW_("-1", obj1.back());
obj1.pop_back();
SHOW_("123", obj1.front());
SHOW_("123", obj1.back());
}
#include <iterator>
void range_for_test() {
std::ostringstream os;
my::static_vector<int, 10> obj1{2, 3, 5, 7, 11, 13, 17, 19,};
#if 0
for (auto it = obj1.cbegin(); it != obj1.cend(); ++it) {
const auto lfd_nr = (it - obj1.cbegin()) + 1;
// const auto lfd_nr = std::distance(obj1.cbegin(), it) + 1;
os << lfd_nr << ": " << *it << " -- "
}
SHOW_("1: 2 -- "
"2: 3 -- "
"3: 5 -- "
"4: 7 -- "
"5: 11 -- "
"6: 13 -- "
"7: 17 -- "
"8: 19 -- ", os.str());
#else
for (auto const& e : obj1) {
os << e << ' ';
}
SHOW_("2 3 5 7 11 13 17 19 ", os.str());
#endif
}
#include <algorithm>
void algorithm_transform_test() {
my::static_vector<int, 10> obj1{2, 3, 5, 7, 11, 13, 17, 19,};
my::static_vector<double, obj1.max_size()> obj2;
std::transform(obj1.cbegin(), obj1.cend(), std::back_inserter(obj2),
[](decltype(obj2)::value_type e) {
return e/2;
});
std::ostringstream os;
for (const auto &e : obj2) {
os << e << ' ';
}
SHOW_("1 1.5 2.5 3.5 5.5 6.5 8.5 9.5 ", os.str());
}
void builtin_transform_test() {
using mv = my::static_vector<int, 10>;
mv obj1{2, 3, 5, 7, 11, 13, 17, 19,};
auto int_descend_cmp =
[](mv::value_type a, mv::value_type b) { return !(a<b); }; // C++11
// [](auto a, auto b) { return !(a<b); }; // C++14
std::ostringstream os;
obj1.transform([=](int* f, int* t) { std::sort(f, t, int_descend_cmp); });
for (const auto &e : obj1) {
os << e << ' ';
}
SHOW_("19 17 13 11 7 5 3 2 ", os.str());
}
int main() {
std::cout.setf(std::ios::boolalpha);
#if 1 // move down to include tests from he beginning
default_ctor_test();
list_ctor_test();
// copy_ctor_test();
// move_ctor_test();
max_size_test();
size_empty_test();
data_size_test();
fast_indexing_test();
safe_indexing_test();
resize_clear_test();
emplace_test();
push_pop_back_test();
front_back_test();
range_for_test();
algorithm_transform_test();
builtin_transform_test();
#endif // move up to include tests from the end
std::cout << "*** TESTING COMPLETED ***" << std::endl;
}
#ifndef MY_STATIC_VECTOR_H
#define MY_STATIC_VECTOR_H
#include <cassert>
#include <initializer_list>
#include <type_traits>
namespace my {
/*
* A class much like `std::vector` but with pre-allocated space
* in a maximum size. As for `std::vector` this container is empty
* at the beginning. Need to be added before they can be accessed.
*/
template <typename T, std::size_t N>
class static_vector {
public:
using value_type = T;
using size_type = decltype(N);
// c'tors, d'tors and assignment
static_vector(std::initializer_list<value_type> const& init)
: static_vector{}
{
assert(init.size() <= max_size()); // check "breach of contract"
for (auto &e : init) {
if (size() == max_size()) break; // <------ for "robustness"
push_back(e);
}
}
~static_vector() { clear(); }
static_vector() =default;
static_vector(const static_vector&) =delete;
static_vector(static_vector&&) =delete;
static_vector& operator=(const static_vector&) =delete;
static_vector& operator=(static_vector&&) =delete;
static constexpr size_type max_size() {
return N;
}
size_type size() const {
assert_in_bounds();
return filled_ - reinterpret_cast<value_type const*>(raw_data_);
}
bool empty() const {
return (size() == 0);
}
value_type* data() {
return reinterpret_cast<value_type*>(raw_data_);
}
value_type const* data() const {
return reinterpret_cast<const value_type*>(raw_data_);
}
value_type& front() {
assert_not_empty();
return reinterpret_cast<value_type*>(raw_data_)[0];
}
value_type const& front() const {
assert_not_empty();
return reinterpret_cast<const value_type*>(raw_data_)[0];
}
value_type& back() {
assert_not_empty();
return filled_[-1];
}
value_type const& back() const {
assert_not_empty();
return filled_[-1];
}
// mimicking the behavior of `std::vector` the index operator as
// implemented below causes undefined behavior for out of bounds
// indices
value_type& operator[](size_type index) {
assert_in_bounds();
return reinterpret_cast<value_type*>(raw_data_)[index];
}
value_type const& operator[](size_type index) const {
assert_in_bounds();
return reinterpret_cast<const value_type*>(raw_data_)[index];
}
// mimicking the behavior of vectors `at` should NOT cause undefined
// behavior (contrary to `operator[]` which may do so) but the proposed
// solution is not REALLY a good one; though without exceptions there
// is not much wigleroom for somehow "curing" the problem as any
// solution chosen will have a tendency to cover up a problem that
// would have better be detected earlier ...
value_type& at(size_type index) {
assert_not_empty();
return reinterpret_cast<value_type*>(raw_data_)[index % size()];
}
value_type const& at(size_type index) const {
assert_not_empty();
return reinterpret_cast<const value_type*>(raw_data_)[index % size()];
}
void push_back(T const& item_to_append) {
assert_not_full();
*(filled_++) = item_to_append;
assert_not_empty();
}
void pop_back() {
assert_not_empty();
(--filled_)->~T();
assert_not_full();
}
template<typename ...Ts>
void emplace_back(Ts&& ...ctor_args) {
assert_not_full();
new (filled_++) value_type{std::forward<Ts>(ctor_args)...};
assert_not_empty();
}
void resize(size_type new_size);
void clear() {
while (!empty()) {
pop_back();
}
}
// providing iterators (we can simply use pointers here)
using iterator = value_type*;
using const_iterator = value_type const*;
iterator begin() {
return reinterpret_cast<value_type*>(raw_data_);
}
iterator end() {
return filled_;
}
const_iterator begin() const {
return reinterpret_cast<const value_type*>(raw_data_);
}
const_iterator end() const {
return filled_;
}
const_iterator cbegin() const {
return reinterpret_cast<const value_type*>(raw_data_);
}
const_iterator cend() const {
return filled_;
}
template<typename F>
void transform(F func) {
func(reinterpret_cast<value_type*>(raw_data_), filled_);
}
private:
// assertions for early error detection in unit tests
// do NOT call public member functions here EXCEPT size()
// so that the below may used freely;
void assert_in_bounds() const {
assert(filled_ >= reinterpret_cast<value_type const*>(raw_data_));
assert(filled_ <= reinterpret_cast<value_type const*>(raw_data_) + N);
}
void assert_not_empty() const {
assert(size() > 0);
}
void assert_not_full() const {
assert(size() < max_size());
}
using storage_type = typename std::aligned_storage<sizeof(T), alignof(T)>::type;
storage_type raw_data_[N];
value_type* filled_{reinterpret_cast<value_type*>(raw_data_)};
};
template<typename T, std::size_t N>
void static_vector<T, N>::resize(size_type new_size) {
static_assert(std::is_unsigned<size_type>::value,
"for signed value_type also `assert(new_size < 0)`");
assert(new_size <= max_size());
while (size() < new_size) {
emplace_back();
}
while (size() > new_size) {
pop_back();
}
}
} // namespace
#endif // include guard