Reference:
https://youtu.be/Fhw43xofyfo?si=PcMFEsqb7NRiCmyX
Two different kinds of sameness
Interface sameness
- Enables users to treat things the same way in their code(create their own sameness)
- Cannot be removed later
- You can't change your user's code(at least not easily)
- Once you allow users to treat things as the same, it's very hard to change that later
- When done well: low code coupling(the degree of interdependence between software modules)
Implementation sameness
- Enables readers to understand and use existing sameness of similarity
- Can be changed or removed at any point in the future when it stops being helpful
- When done well: high code cohesion(the degree to which the elements inside a module belong together)
Let this concept sinks in.
Reusing customization points.
Mixin mixins.
template<class> struct CRTPMixin;
template<template <class> class Mixin, class Derived>
struct CRTPMixin<Mixin<Derived>> {
consteval auto& self() { return static_cast<Derived&>(*this); }
consteval auto const& self() const { return static_cast<Derived const&>(*this); }
};
template<typename Derived>
struct PrintableElementwise : CRTPMixin<PrintableElementwise<Derived>> {
void print() const {
auto const& e = self().elements();
std::apply([](auto const&... el) {
([&]{ cout << el << "\n"; }(), ...);
}, e);
}
};
As stated by Sy Brand's C++23’s Deducing this can help with the above
https://devblogs.microsoft.com/cppblog/cpp23-deducing-this/static_cast<Derived const&>(*this);
part. But really? Consider code uses deduce this from C++23:
struct PrintableElementwise {
void print(this auto const& self) {
auto const& e = self.elements();
std::apply([](auto const&... el) {
([&]{ count << el << "\n"; }(), ...);
}, e);
}
};
struct Foo : PrintableElementwise {
int a;
double b;
std::string s;
auto elements() const {
return std::forward_as_tuple(x, y, z);
}
};
Above has mixed interface sameness with implementation sameness; which causes error:auto items = std::vector<PrintableElementwise>{/*...*/};
for (auto* i : items) {
i->print(); // error if derived type has no memfn elements().
}
Use the idea of 'type of T'. Which indicates the type's embedded behavior.
Qualifier forwarding
e.g.
Perfect forwarding:
template<class T>
void foo(T&& t) {
bar(std::forward<T>(t));
}
equivalent to:
template<class T>
void foo(T& t) {
bar(t);
}
template<class T>
using not_forwarding_ref = T;
template<class T>
void foo(not_forwarding_ref<T>&& t) {
bar(std::move(t));
}
Don't Repeat Yourself(DRY)
'Every piece of knowledge must have a single, unambiguous, authoritative representation within a system'Separating the ownership mechanism (typical pattern for a class template customization point)
From:
template<class Thing>
class OwingCollection {
private:
vector<unique_ptr<Thing>> things_;
protected:
void for_each(/* concept */ std::invocable<Thing const&> auto&& f) const { /*...*/};
public:
void insert(unique_ptr<Thing>) {};
unique_ptr<Thing> remove(unique_ptr<Thing>) {};
bool contains(unique_ptr<Thing>) const {};
void remove_if(invocable<Thing const&> auto&&) {};
};
To:template<class Thing, template<class...> class Owner = unique_ptr>
class OwingCollection {
private:
vector<Owner<Thing>> things_;
protected:
void for_each(/* concept */ std::invocable<Thing const&> auto&& f) const { /*...*/};
public:
void insert(Owner <Thing>) {};
Owner <Thing> remove(Owner <Thing>) {};
bool contains(Owner <Thing>) const {};
void remove_if(invocable<Thing const&> auto&&) {};
};
STD's example of separable pattern
[C++14] unique_ptr with type erasure as shared_ptr
make_unique
https://isocpp.org/files/papers/n3588.txt
C++20 Concepts
C++20 concepts extract interface sameness without your permission.
Concepts allow library users to accidentally create code coupling between unrelated modules based only on names.
e.g.
template<class T>
requires requires(T&& t) { { t.clear() } -> convertible_to<bool>; }
void do_the_stuff(T&& t) { /*...*/ }
Those two types fit for above API. However; their meaning of memfn clear() is different.
struct Container {
// returns true if the container was
// non-empty before the clear
bool clear();
};
struct Color {
// returns true if opacity == 0
bool clear();
};
Concepts does not differentiate namespace.More places having the concept of 'sameness'
- 'Normal' functions (C like)
- Macros and Code generation
- 'Gross' but sometimes better than repeating things.
- Customization Point Objects(CPOs)
- Type erasure
- constexpr functions
- 'Sameness' of compile-time and runtime implementations.
- Dependency injection (needs reflection)
- Aspect-oriented programming(needs reflection)
- Decoration(needs reflection)
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.