Normal lists in C++ are either C-style arrays or standard-library containers (e.g. std::vector
, std::array
, std::list
). We want to create a more powerful list structure in C++. We want to create one that can exhibit more interesting behaviours. For example:
auto l1 = smart_list<double>{1,2,3};auto l2 = smart_list<double>{4,5,6};auto l3 = l1 + l2;REQUIRE(l3 == smart_list<double>{1,2,3,4,5,6})
In rare cases we want a list that is very smart and can do everything a normal smart_list
can do, but can also sometimes make use of particular properties of our types:
auto l = very_smart_list<int>{6,9,15};l /= 3;REQUIRE(l == very_smart_list<int>{2,3,5});REQUIRE(l.is_prime());
A smart_list
should not be able to do these things: it's not smart enough.
Please note that the usage of "list" here is quite high level, and does not specifically refer to a linked list. In most cases "list" in higher level languages just refers to an object similar to std::vector
.
You are to develop a new class template smart_list<T>
that behaves similar to a std::vector<T>
except that it has a handful of modified funtionality and additional functionality. You are also to develop a new class very_smart_list<T>
which can do everything a smart_list<T>
can do, with more.
The interface of these classes are described below. Where functions are provided for both smart_list
and very_smart_list
, the use of "smart_list
" should be replaced by "very_smart_list
" when appropriate except when otherwise mentioned.
There are no constraints regarding internal representation or implement. As long as the interface functions as described that is all that matters. You are allowed to have more functions on the public interface than we have described (if this helps you solve the question).
Both smart_list
and very_smart_list
:
explicit smart_list(std::size_t count)
/ explicit very_smart_list(std::size_t count)
count
default-constructed elements into the list.auto x = smart_list<int>(5);
smart_list(T const& one, T const& two, std::size_t count)
/ very_smart_list(T const& one, T const& two, std::size_t count)
count
repetitions of appending both one
and two
to the list.auto x = smart_list<std::string>("hello", "there", 3)
would produce a list containing ["hello", "there", "hello", "there", "hello", "there"]
.one == two
, throws "Cannot use multi-argument constructor with two identical elements"
smart_list(std::initializer_list<T> il)
/ very_smart_list(std::initializer_list<T> il)
auto l = smart_list<double>{ 1.0, 1.5, 2.0 };
For very_smart_list
only:
explicit very_smart_list(smart_list<T> const& other)
other
.auto vsl = very_smart_list<double>(sl);
explicit very_smart_list(smart_list<T>&& other)
other
.auto vsl = very_smart_list<double>(std::move(sl));
Both smart_list
and very_smart_list
:
std::size_t size()
auto x = sl.size();
Both smart_list
and very_smart_list
:
void push_back(const T& value)
sl.push_back(5);
void pop_back()
sl.pop_back();
sl.size() == 0
, throws "Cannot pop_back an empty list"
.void emplace_back(Args... args)
auto sl = smart_list<std::pair<int, double>>(); sl.emplace_back(1, 2.0);
Both smart_list
and very_smart_list
:
operator[](std::size_t index)
auto a = sl[1]; sl[2] = a + 2;
smart_list operator+(smart_list const& lhs, smart_list const& rhs)
auto c = a + b;
lhs.size() == 0
or rhs.size() == 0
, throw "Cannot concatenate smart lists where one is empty"
.smart_list
, even when called on very_smart_list
s.smart_list operator-(smart_list const& lhs, smart_list const& rhs)
lhs
, with all elements also present in rhs
removed.auto c = a - b;
lhs.size() == 0
or rhs.size() == 0
, throw "Cannot subtract smart lists where one is empty"
.operator==
to compare elements. Always returns a smart_list
, even when called on very_smart_list
s.smart_list<smart_list<T>> operator*(smart_list const& lhs, smart_list const& rhs)
auto a = smart_list<double>{1,2,3};auto b = smart_list<double>{2,3,4};auto c = a * b;REQUIRE(c == smart_list<smart_list<double>>{{2,3,4},{4,6,8},{6,9,12}});
operator*
to multiple elements. Always returns a nested smart_list
, even when called on very_smart_list
s.std::ostream& operator<<(std::ostream& os, smart_list const& sl)
sl
to os
with a |
on either side of the element. An empty list should print nothing.auto sl = smart_list<double>{1,2,3};std::cout << sl; // |1|2|3|
For very_smart_list
only:
very_smart_list operator*(very_smart_list const& lhs, double scalar)
[1 2] * 3 = 3 * [1 2] = [3 6]
. If T
is not an arithmetic type, then this function has no effect and simply returns a copy of the list passed in.auto x = sl * 3.0; auto y = 3.0 * sl;
very_smart_list operator/(very_smart_list const& lhs, double scalar)
[3.0 6.0] / 2.0 = [1.5 3.0]
. If T
is not an arithmetic type, then this function has no effect and simply returns the list passed in.auto x = sl / 3.0;
For very_smart_list
only:
bool is_prime()
false
if T
is not an integer type. Otherwise, returns true
iff all of the elements in the container are prime numbers.auto x = vsl.is_prime();
You are also required to ensure that both classes have the following capabilities:
operator==
and operator!=
with the normal meanings (assuming that T
is also comparable)operator+=
too). It should throw the same exceptions as well.begin()
and end()
that returns a type which models at least std::bidirectional_iterator
and act as iterators to your underlying container. You can assume begin()
and end()
return only const_iterator
-like types (no non-const iterators).You should be able to use a very_smart_list
wherever a smart_list
is expected. For example:
auto sl = smart_list<double>{1,2,3};auto vsl = very_smart_list<double>(sl);vsl *= 3.0;REQUIRE(sl + vsl == smart_list<double>{1,2,3,3,6,9});
However, you should not be able to use a smart_list
where a very_smart_list
is expected:
auto sl = smart_list<double>{1,2,3};sl *= 3.0; // should be a compiler error
You are required to ensure that relevant operators are marked const where appropriate, or have both a const and non-const version where appropriate.
All exceptions thrown are std::runtime_error
. The use of "Exceptions: None." just means you do not need to throw any exceptions yourself: you do not need to handle exceptions thrown by operations on the types stored.
Please ensure that as a priority you ensure that operator==
, push_back
, and default construction work. We will rely on these 3 heavily for testing.
For this question, the approximate mark allocations are:
smart_list
very_smart_list
(everything from smart_list
plus additional functions)very_smart_list
to be used everywhere a smart_list
might be expected