|
|
@ -0,0 +1,231 @@ |
|
|
|
## Exercise 1
|
|
|
|
|
|
|
|
```cpp |
|
|
|
// Exercise 1 |
|
|
|
// |
|
|
|
// Try to read and understand the following commented code |
|
|
|
// Run it and try to break it and extend it with new features, I want you to explore it. |
|
|
|
// Try to point the features that you identified in the categories of things in the education plan |
|
|
|
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
#include <vector>
|
|
|
|
#include <optional>
|
|
|
|
#include <variant>
|
|
|
|
|
|
|
|
// templates allow to create several copies of a class with different type parameters replaced inside for everything |
|
|
|
// that we need replaced |
|
|
|
template<typename NodeData, typename EdgeData> |
|
|
|
struct Graph { |
|
|
|
private: |
|
|
|
// It is possible to nest type declarations, this is useful when types depend on other types, or when you want to |
|
|
|
// hide a type |
|
|
|
struct Node { |
|
|
|
// https://en.cppreference.com/w/cpp/language/attributes |
|
|
|
[[no_unique_address]] NodeData value; |
|
|
|
std::vector<std::pair<size_t, EdgeData>> edges; |
|
|
|
}; |
|
|
|
// Sentinel iterators |
|
|
|
// https://www.foonathan.net/2020/03/iterator-sentinel/ |
|
|
|
struct EndNodeRef; |
|
|
|
struct NodeRef { |
|
|
|
NodeRef& operator++() { |
|
|
|
++idx; |
|
|
|
// "this" is a pointer to the current object, it should have been a reference, but they didn't exist when |
|
|
|
// "this" was added to C++ |
|
|
|
return *this; |
|
|
|
} |
|
|
|
|
|
|
|
NodeRef operator+(size_t n) { |
|
|
|
// this implements "some_node_ref + number", it however doesn't implement "number + some_node_ref" |
|
|
|
auto cpy = *this; |
|
|
|
cpy.idx += n; |
|
|
|
return cpy; |
|
|
|
} |
|
|
|
|
|
|
|
bool operator==(const EndNodeRef&) const { |
|
|
|
// Compare with sentinel means check if we are out of bounds |
|
|
|
return idx >= owner.m.data.size(); |
|
|
|
} |
|
|
|
|
|
|
|
// https://www.cppstories.com/2023/monadic-optional-ops-cpp23/ |
|
|
|
std::optional<NodeData*> operator*() { |
|
|
|
return index().transform([](auto* v) -> NodeData* { return &(v->value);}); |
|
|
|
} |
|
|
|
|
|
|
|
// "using" is used to define type aliases. This aliases "std::vector<std::pair<size_t, EdgeData>>" into the name |
|
|
|
// "edge_list" |
|
|
|
using edge_list = std::vector<std::pair<size_t, EdgeData>>; |
|
|
|
|
|
|
|
std::optional<const edge_list> edges() { |
|
|
|
if(owner.m.data.size() > idx) { |
|
|
|
return owner.m.data[idx].edges; |
|
|
|
} |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
|
|
|
|
bool link_edge(const NodeRef& link_to, const EdgeData& data) { |
|
|
|
// In C++, you can define a function that will "capture" the local scope. Called a lambda, those functions |
|
|
|
// are actually internally a struct with an operator() and the captured members are its contents. |
|
|
|
// [capture](arguments){code...} is the basic syntax. |
|
|
|
// if capture is = "[=](arguments){code...}" then everything that is captured is copied in the lambda |
|
|
|
// if capture is & "[&](arguments){code...}" then everything that is captured as a reference to the original |
|
|
|
// value in the lambda |
|
|
|
// if capture is empty "[](arguments){code...}" then nothing is captured |
|
|
|
auto v = index().transform([&](Node* node) -> bool { |
|
|
|
// We need to verify if both nodes are in the same graph, for that we use the "addressof" function that |
|
|
|
// returns where in memory the data is sitting |
|
|
|
if(link_to.idx >= owner.size() or std::addressof(link_to.owner) != std::addressof(owner)) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
// -> is used when the left side is a pointer or smart pointer |
|
|
|
// . is used when the left side is a value or a reference |
|
|
|
node->edges.push_back({link_to.idx, data}); |
|
|
|
return true; |
|
|
|
}); |
|
|
|
return v.value_or(false); |
|
|
|
} |
|
|
|
|
|
|
|
// Everything in the scope that follows "private:" is hidden from the outside, except to friends who can still access it |
|
|
|
private: |
|
|
|
// https://en.cppreference.com/w/cpp/language/constructor |
|
|
|
NodeRef(Graph& graph, size_t in_idx) |
|
|
|
: owner{graph} |
|
|
|
, idx{in_idx} |
|
|
|
{} |
|
|
|
|
|
|
|
std::optional<Node*> index() { |
|
|
|
// Thanks to optional, all the unsafe things you can do are contained in this function |
|
|
|
if(owner.size() > idx) { |
|
|
|
return std::addressof(owner.m.data[idx]); |
|
|
|
} |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
|
|
|
|
// References MUST be initialized, so the constructor is REQUIRED to initialize it |
|
|
|
Graph& owner; |
|
|
|
size_t idx; |
|
|
|
|
|
|
|
// https://en.cppreference.com/w/cpp/language/friend |
|
|
|
friend Graph; |
|
|
|
}; |
|
|
|
|
|
|
|
struct EndNodeRef{ |
|
|
|
bool operator==(const NodeRef& value) const { |
|
|
|
// implementing the flipped operator, by using the other one |
|
|
|
return value == *this; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// Everything in the scope that follows "public:" is visible from the outside |
|
|
|
public: |
|
|
|
NodeRef begin() { |
|
|
|
// the first index is 0 |
|
|
|
return NodeRef{*this, 0}; |
|
|
|
} |
|
|
|
|
|
|
|
EndNodeRef end() { |
|
|
|
// the "last" index is the sentinel |
|
|
|
return EndNodeRef{}; |
|
|
|
} |
|
|
|
|
|
|
|
size_t size() { |
|
|
|
return m.data.size(); |
|
|
|
} |
|
|
|
|
|
|
|
NodeRef insert(const NodeData& data) { |
|
|
|
m.data.push_back(Node{.value = data, .edges = {}}); |
|
|
|
// reference to the last thing (that we just inserted) is the one at the position size() - 1 (because we index |
|
|
|
// from 0 |
|
|
|
return NodeRef{*this, size() - 1}; |
|
|
|
} |
|
|
|
|
|
|
|
private: |
|
|
|
// Protected constructor |
|
|
|
// https://www.youtube.com/watch?v=KWB-gDVuy_I |
|
|
|
struct internals { |
|
|
|
std::vector<Node> data; |
|
|
|
}; |
|
|
|
internals m; |
|
|
|
}; |
|
|
|
|
|
|
|
int main() { |
|
|
|
// std::monostate is just an empty type (hence, there is only one state it can be in, hence "monostate") |
|
|
|
Graph<std::string, std::monostate> graph; |
|
|
|
auto one = graph.insert("one"); |
|
|
|
auto two = graph.insert("two"); |
|
|
|
auto three = graph.insert("three"); |
|
|
|
auto four = graph.insert("four"); |
|
|
|
one.link_edge(two, {}); |
|
|
|
one.link_edge(three, {}); |
|
|
|
one.link_edge(four, {}); |
|
|
|
two.link_edge(four, {}); |
|
|
|
three.link_edge(four, {}); |
|
|
|
|
|
|
|
// Before range-for loops, this is how we iterated over things, since the class above doesn't have a good interface |
|
|
|
// for range-for loops, we need to do it the old way |
|
|
|
for(auto it = graph.begin(); it != graph.end(); ++it) { |
|
|
|
// decltype(A) gives you the type of the variable A, for example here, it returns the correct type of NodeRef |
|
|
|
auto edge_list = it.edges().value_or(decltype(it)::edge_list{}); |
|
|
|
std::cout << (*it).transform([](auto* v) { return *v;}).value_or("[UNKNOWN]") << " links to "<< edge_list.size() << " node(s):\n"; |
|
|
|
for(const auto& edge : edge_list) { |
|
|
|
std::cout << "\t" << (*(graph.begin() + edge.first)).transform([](auto* v) { return *v;}).value_or("[UNKNOWN]") << "\n"; |
|
|
|
} |
|
|
|
} |
|
|
|
return 0; |
|
|
|
} |
|
|
|
``` |
|
|
|
|
|
|
|
### Ed. Plan
|
|
|
|
|
|
|
|
- The main function |
|
|
|
- namespaces |
|
|
|
- types |
|
|
|
- basic types |
|
|
|
- fixed types |
|
|
|
- containers |
|
|
|
- iterating |
|
|
|
- algorithms |
|
|
|
- rotate |
|
|
|
- swap |
|
|
|
- partition |
|
|
|
- sort |
|
|
|
- template metaprogramming making your own |
|
|
|
- classes |
|
|
|
- accessibility |
|
|
|
- basic classes |
|
|
|
- virtual |
|
|
|
- template classes |
|
|
|
- c++ compilation |
|
|
|
- header vs source |
|
|
|
- preprocessor |
|
|
|
- compiler |
|
|
|
- linker |
|
|
|
- errors |
|
|
|
- non-errors |
|
|
|
- optional |
|
|
|
- variant |
|
|
|
- (expected) |
|
|
|
- errors |
|
|
|
- exceptions |
|
|
|
- signals |
|
|
|
- exit/terminate/abort |
|
|
|
- compile time shenanigans |
|
|
|
- template metaprogramming |
|
|
|
- traits |
|
|
|
- concepts |
|
|
|
- (if) constexpr |
|
|
|
- memory |
|
|
|
- smart pointers |
|
|
|
- raw pointers |
|
|
|
- move, copy, constructors |
|
|
|
- references, rvalue reference etc |
|
|
|
- doin' some weird |
|
|
|
- ADL |
|
|
|
- casts |
|
|
|
- attributes |
|
|
|
- C linkage |
|
|
|
- UB |
|
|
|
- variadic templates |
|
|
|
- Rule of 0, 3, 5 |
|
|
|
- pImpl |