vector::back
Calling
back
on an empty container is undefined.
vector
The requirements that are imposed on the elements depend on the actual operations performed on the container. Generally, it is required that element type meets the requirements of
Erasable
, but many member functions impose stricter requirements. This container (but not its members) can be instantiated with an incomplete element type if the allocator satisfies the allocator completeness requirements.
X(a)
[copy construction], Requires:T
isCopyInsertable
intoX
vector
a.back()
; Operational semantics:
{ auto tmp = a.end(); --tmp; return *tmp; }
CopyInsertable
The value of
v
is unchanged and is equivalent to*p
After container move construction, references, pointers, and iterators (other than the end iterator) to
other
remain valid, but refer to elements that are now in*this
.
!(capacity() < size())
distance(begin(), end()) == size()
Execute with all meaningful combinations of representative values
Check postconditions, axioms, and invariants after API execution
Use counters to test complexity and external failure points
Use a model type with static_assert
to test type requirements
model
type which counts operations to measure complexitystruct {
size_t _equality;
size_t _ctor;
size_t _move_ctor;
size_t _copy_ctor;
size_t _copy_assignment;
size_t _move_assignment;
size_t _dtor;
} _counters;
model
type is parameterized for the required operationsenum operations {
moveable = 1 << 0,
copyable = 1 << 1,
equality_comparable = 1 << 2
};
template <operations Ops>
struct model {
int _value = 0; // 0 flags partially formed
explicit model(int x) : _value(x) { ++_counters._ctor; }
model(model&& x) noexcept : _value(x._value) {
static_assert(Ops & moveable);
x._value = 0;
++_counters._move_ctor;
}
model& operator=(model&& x) {
static_assert(Ops & moveable);
REQUIRE(x._value || !(x._value || _value));
_value = x._value;
x._value = 0;
++_counters._move_assignment;
return *this;
}
//...
static constexpr auto equality = [](const auto& a, const auto& b) {
return a._value == b._value;
};
};
template <class C>
void test_invariants(const C& a) {
REQUIRE(a.empty() == (a.begin() == a.end()));
REQUIRE(distance(a.begin(), a.end()) == a.size());
REQUIRE(!(a.capacity() < a.size()));
}
template <class C, class I, class T>
void test_move_insert(
const C& initial, const T& initial_value, C& container, I position, T value) {
auto counters = _counters;
bool has_capacity = container.size() < container.capacity();
auto ip = begin(initial) + distance(begin(container), position);
auto p = container.insert(position, move(value));
// Test invariants
test_invariants(container);
// Test postconditions
REQUIRE(equal(begin(initial), ip, begin(container), p, T::equality));
REQUIRE(equal(ip, end(initial), next(p), end(container), T::equality));
REQUIRE(T::equality(*p, initial_value));
if (has_capacity) {
REQUIRE(p == position);
}
// Test complexity
auto move_count =
(has_capacity ? distance(ip, end(initial)) : initial.size()) + 1;
REQUIRE((_counters._move_ctor - counters._move_ctor) <= move_count);
}
template <class C, class T>
auto build_vector(const T& a, size_t capacity) {
C r;
r.reserve(capacity);
for (auto& e : a)
r.emplace_back(e);
return r;
}
void test_move_insert_driver() {
using value_t = model<moveable>;
constexpr int a[] = {1, 2, 3};
auto v1 = build_vector<vector<value_t>>(a, size(a));
size_t positions[] = {0, size(a) / 2, size(a)}; // begin, middle, end
// insert each position without sufficient capacity
for (const auto& e : positions) {
auto v2 = build_vector<vector<value_t>>(a, size(a));
test_move_insert(v1, value_t{4}, v2, begin(v2) + e, value_t{4});
}
// insert each position with sufficient capacity
for (const auto& e : positions) {
auto v2 = build_vector<vector<value_t>>(a, size(a) + 1);
test_move_insert(v1, value_t{4}, v2, begin(v2) + e, value_t{4});
}
// insert into an empty vector
{
vector<value_t> v2;
test_move_insert(vector<value_t>(), value_t{4}, v2, begin(v2), value_t{4});
}
}
test_move_insert_driver();
For many operations and types you can write generic test case
Refine how you manage tables of representative values so you can reuse the structure with different tables for different tests
For a given type, you can write a single test for invariants and use it after each operation