# Implement a Simple Vector Class
**Interview Question: Expression Templates and Compile-Time Polymorphism**
**Background:**
Expression templates are a C++ template metaprogramming technique used for optimizing high-performance computations, particularly in numerical computing and scientific applications. They enable the efficient evaluation of complex expressions by eliminating unnecessary temporaries and combining multiple operations into a single loop at compile time.
**Task:**
1. **Implement a Simple Vector Class:**
- Create a template class `Vector` that encapsulates a dynamic array of elements (use `std::vector` for storage).
- Implement basic operations like element access and size retrieval.
2. **Expression Templates for Vector Operations:**
- Extend the `Vector` class to support addition and subtraction operations with other vectors.
- Use expression templates to create and return intermediate expression objects instead of computing the result immediately.
- Ensure that the actual computation of the addition or subtraction happens only when the result is assigned to a vector.
3. **Compile-Time Polymorphism:**
- Utilize compile-time polymorphism to handle different types of expressions (e.g., addition, subtraction) in a generic way.
- Demonstrate the use of template specialization or SFINAE (Substitution Failure Is Not An Error) to distinguish between different expression types.
4. **C++20 Concepts:**
- Define a concept that ensures the template parameters for your vector and expression templates are only instantiated with compatible types.
- Use `requires` clauses to enforce these constraints.
5. **Custom Type Traits and Reflection (Bonus):**
- Implement a custom type trait to check if a given type is an instance of your `Vector` class.
- Demonstrate how you can use this trait in SFINAE or `requires` clauses to create more flexible and type-safe code.
**Evaluation Criteria:**
- Correct implementation of expression templates, ensuring efficient computation.
- Proper use of C++20 concepts and `requires` clauses for type safety.
- Effective use of compile-time polymorphism and SFINAE.
- Bonus points for implementing and using custom type traits.
- Code readability, documentation, and adherence to C++ best practices.
## Step 1
1. **Implement a Simple Vector Class:**
- Create a template class `Vector` that encapsulates a dynamic array of elements (use `std::vector` for storage).
- Implement basic operations like element access and size retrieval.
Let's include headers first!
```cpp!
#include <vector>
#include <cstdint>
```
```cpp!
template<typename T>
class Vector {
std::vector<T> storage_;
public:
// Actually, we can let the compiler generate those special member functions
// by itself, because according to the [dcl.fct.def.default] p5 says...
Vector() = default; // C++11
Vector(const Vector&) = default;
Vector(Vector&&) = default;
Vector& operator=(const Vector&) = default;
Vector& operator=(Vector&&) = default;
~Vector() = default;
const T& operator[](size_t pos) const { return storage_[pos]; }
T& operator[](size_t pos) { return storage_[pos]; }
size_t size() const noexcept { return storage_; }
};
```
## Step 2
2. **Expression Templates for Vector Operations:**
- Extend the `Vector` class to support addition and subtraction operations with other vectors.
- Use expression templates to create and return intermediate expression objects instead of computing the result immediately.
- Ensure that the actual computation of the addition or subtraction happens only when the result is assigned to a vector.
Expression Template:
```cpp!
template<typename T>
class Vector;
template<typename E, typename T>
class VecExpression {
public:
size_t size() const { return static_cast<const E&>(*this).size(); }
T operator[](size_t i) const { return static_cast<const E&>(*this)[i]; }
operator Vector<T>() const {
Vector<T> result(size());
for (size_t i = 0; i < size(); ++i) {
result[i] = (*this)[i];
}
return result;
}
};
```
The modified Vector class:
```cpp!
template<typename T>
class Vector : public VecExpression<Vector<T>, T> {
std::vector<T> storage_;
public:
Vector() = default;
Vector(size_t n) : storage_(n) {}
Vector(const Vector<T>&) = default;
Vector(Vector<T>&&) = default;
Vector& operator=(const Vector<T>&) = default;
Vector& operator=(Vector<T>&&) = default;
~Vector() = default;
template<typename E>
Vector(const VecExpression<E, T>& expr) {
storage_.resize(expr.size());
for (size_t i = 0; i < expr.size(); ++i) {
storage_[i] = expr[i];
}
}
template<typename E>
Vector& operator=(const VecExpression<E, T>& expr) {
storage_.resize(expr.size());
for (size_t i = 0; i < expr.size(); ++i) {
storage_[i] = expr[i];
}
return *this;
}
size_t size() const noexcept { return storage_.size(); }
T& operator[](size_t i) { return storage_[i]; }
const T& operator[](size_t i) const { return storage_[i]; }
};
```
Addition Expression:
```cpp!
template<typename T>
class VecAdd : public VecExpression<VecAdd<T>, T> {
const Vector<T>& lhs_;
const Vector<T>& rhs_;
public:
VecAdd(const Vector<T>& lhs, const Vector<T>& rhs) : lhs_(lhs), rhs_(rhs) {
assert(lhs.size() == rhs.size()); // We couldn't handle the size mismatching issue
}
size_t size() const noexcept { return lhs_.size(); }
T operator[](size_t i) const { return lhs_[i] + rhs_[i]; }
};
template<typename T>
VecAdd<T> operator+(const Vector<T>& lhs, const Vector<T>& rhs) {
return VecAdd<T>(lhs, rhs);
}
```
Subtraction Expression:
```cpp!
template<typename T>
class VecSub : public VecExpression<VecSub<T>, T> {
const Vector<T>& lhs_;
const Vector<T>& rhs_;
public:
VecSub(const Vector<T>& lhs, const Vector<T>& rhs) : lhs_(lhs), rhs_(rhs) {
assert(lhs.size() == rhs.size());
}
size_t size() const noexcept { return lhs_.size(); }
T operator[](size_t i) const { return lhs_[i] - rhs_[i]; }
};
template<typename T>
VecSub<T> operator-(const Vector<T>& lhs, const Vector<T>& rhs) {
return VecSub<T>(lhs, rhs);
}
```
See: https://godbolt.org/z/646nWK8e1
## Step 3
3. **Compile-Time Polymorphism:**
- Utilize compile-time polymorphism to handle different types of expressions (e.g., addition, subtraction) in a generic way.
- Demonstrate the use of template specialization or SFINAE (Substitution Failure Is Not An Error) to distinguish between different expression types.
We use the print-function for example, please also feel free replacing to your requrement:
```cpp!
template<typename Expr, typename = void>
struct printVecExpressionType {
static void print([[maybe_unused]] const Expr& e) noexcept {
std::cout << "Unknown Expression Type" << std::endl;
}
};
template<typename T>
struct printVecExpressionType<VecAdd<T>, std::void_t<decltype(std::declval<VecAdd<T>>().size())>> {
static void print(const VecAdd<T>& e) noexcept {
using size_type = decltype(e.size());
for (size_type i = 0; i < e.size(); i++)
std::cout << e[i] << " ";
std::cout << std::endl;
}
};
template<typename T>
struct printVecExpressionType<VecSub<T>, std::void_t<decltype(std::declval<VecSub<T>>().size())>> {
static void print(const VecSub<T>& e) noexcept {
using size_type = decltype(e.size());
for (size_type i = 0; i < e.size(); i++)
std::cout << e[i] << " ";
std::cout << std::endl;
}
};
```
See: https://godbolt.org/z/MhY4bTnoq
## Step 4
4. **C++20 Concepts:**
- Define a concept that ensures the template parameters for your vector and expression templates are only instantiated with compatible types.
- Use `requires` clauses to enforce these constraints.
We need C++20's concept header:
```cpp!
#include <concepts> // C++20
#include <type_traits> // C++11
```
The concept:
```cpp!
template<typename T>
concept ArithmeticType = requires(T a, T b) {
{ a + b } -> std::convertible_to<T>;
{ a - b } -> std::convertible_to<T>;
{ a == b } -> std::convertible_to<bool>;
{ a != b } -> std::convertible_to<bool>;
};
```
And we replace the Vector, VecAdd, VecSub `typename` to our concepts.
See: https://godbolt.org/z/z483b8aqs
## Step 5
5. **Custom Type Traits and Reflection (Bonus):**
- Implement a custom type trait to check if a given type is an instance of your `Vector` class.
- Demonstrate how you can use this trait in SFINAE or `requires` clauses to create more flexible and type-safe code.
Implement our type traits:
```cpp!
template<typename T>
struct IsVector : std::false_type {};
template<typename T>
struct IsVector<Vector<T>> : std::true_type {};
template<typename T>
inline constexpr bool is_vector_v = IsVector<T>::value;
```
### SFINAE version:
```cpp!
template<typename T>
typename std::enable_if<is_vector_v<T>, void>::type
printVector(const T& vec) {
for (size_t i = 0; i < vec.size(); ++i) {
std::cout << vec[i] << " ";
}
std::cout << std::endl;
}
template<typename T>
typename std::enable_if<!is_vector_v<T>, void>::type
printVector(const T& vec) {
std::cout << "Not a Vector type" << std::endl;
}
```
### concept version:
```cpp!
template<typename T>
concept VectorType = is_vector_v<T>;
template<typename T>
concept VecExpressionType = std::is_base_of_v<VecExpression<T, decltype(std::declval<T>().operator[](0))>, T>;
template<typename T>
concept VectorGeneralType = VecExpressionType<T> || VectorType<T>;
template<VectorGeneralType T>
void printVector(const T& vec) {
for (size_t i = 0; i < vec.size(); ++i) {
std::cout << vec[i] << " ";
}
std::cout << std::endl;
}
// Other types
template<typename T>
void printVector([[maybe_unused]] const T& vec) requires (!VectorGeneralType<T>) {
std::cout << "Not a Vector type" << std::endl;
}
```
See: https://godbolt.org/z/cMhcTnM4T
Thanks!