|
|
@@ -16,6 +16,48 @@
|
|
|
#include "comparable.hpp"
|
|
|
|
|
|
namespace types {
|
|
|
+ /**
|
|
|
+ * A class that allows you to perform a typedef without the risk of
|
|
|
+ * accidentally changing the meaning of a type due to negligence. This is
|
|
|
+ * useful instead of manually creating a wrapper class because it allows you
|
|
|
+ * to automatically generate the majority of interesting functions that the
|
|
|
+ * type needs to work without needing to handle boilerplate yourself. Suppose,
|
|
|
+ * for example, I have a collection of items that are indexed either in the
|
|
|
+ * local datastore, or in the external datastore. One option would be to use a
|
|
|
+ * 'tagged union', but this does nothing to allow someone to misuse the index
|
|
|
+ * at a lower point in code if we pass down only the index. We can't use a
|
|
|
+ * variant unless the index types are different, or we can use opaque_typedef:
|
|
|
+ * \code
|
|
|
+ * using local_index = opaque_typedef<int, struct local_index_t, ...>;
|
|
|
+ * using external_index = opaque_typedef<int, struct external_index_t, ...>;
|
|
|
+ * struct item {
|
|
|
+ * std::variant<local_index, external_index> index;
|
|
|
+ * };
|
|
|
+ * \endcode
|
|
|
+ * This means that we can defer unwrapping the index until we leave the
|
|
|
+ * module, guaranteeing that we only use local_index for the local datastore
|
|
|
+ * code paths, and external_index for the external datastore's.
|
|
|
+ *
|
|
|
+ * Additional features are aquired primarily via mixins with the Skills
|
|
|
+ * template arguments. Provided skills are named similarly to Concepts:
|
|
|
+ * - EqualityComparable: can perform operator== and operator!=
|
|
|
+ * - Comparable: supports all 6 comparison operators
|
|
|
+ * - Incrementable: suports operator++ prefix and postfix
|
|
|
+ * - Decrementable: suports operator-- prefix and postfix
|
|
|
+ * - Addable: supports binary addition on itself
|
|
|
+ * - Arithmetic: supports binary addition and subtraction on itself, as well
|
|
|
+ * as unary negation
|
|
|
+ * - Numeric: supports binary addition, subtraction, multiplication, and
|
|
|
+ * division on itself, as well as unary negation
|
|
|
+ *
|
|
|
+ * An important distinction here is that you should not use opaque_typedef in
|
|
|
+ * situations where you need to provide non-default implementations of
|
|
|
+ * operations. For example, you would not make an opaque typedef to define a
|
|
|
+ * file path, because path + path should generally perform path concatenation,
|
|
|
+ * and thus may include an additional path.separator.
|
|
|
+ * However, it would be valid to use opaque typedefs to differentiate between,
|
|
|
+ * say, local files and remote files.
|
|
|
+ */
|
|
|
template <typename Base, typename Tag, template <typename> class... Skills>
|
|
|
class opaque_typedef
|
|
|
: public Skills<opaque_typedef<Base, Tag, Skills...>>... {
|
|
|
@@ -23,13 +65,44 @@ namespace types {
|
|
|
Base value_;
|
|
|
|
|
|
public:
|
|
|
+ /**
|
|
|
+ * By necessity - opaque typedefs must provide a default constructor so that
|
|
|
+ * they can be used in containers and other contexts where we can't always
|
|
|
+ * provide direct construction.
|
|
|
+ */
|
|
|
opaque_typedef() = default;
|
|
|
+ /**
|
|
|
+ * An opaque_typedef does not permit implicit conversion, because the
|
|
|
+ * purpose is to avoid accidentaly mixing up multiple arguments. For
|
|
|
+ * example: draw_rect(int x, int y, int w, int h) is easily mistakable,
|
|
|
+ * whereas draw_rect(position x, position y, length w, length h) is easier
|
|
|
+ * to reason about, and half as error-prone due to limitting the number of
|
|
|
+ * mixups
|
|
|
+ */
|
|
|
explicit opaque_typedef(Base const & value) : value_(value){};
|
|
|
explicit opaque_typedef(Base && value)
|
|
|
: value_(std::forward<Base>(value)){};
|
|
|
+ /**
|
|
|
+ * Implicit conversion between multiple opaque_typedef types can be
|
|
|
+ * accomplished by defining this constructor for the two types. The weakness
|
|
|
+ * of this is that improper conversions will produce linker errors instead
|
|
|
+ * of compiler errors, but this is the cleanes implementation method. Create
|
|
|
+ * a converting constructor as follows:
|
|
|
+ * \code
|
|
|
+ * using TypeA = opaque_typedef<...>;
|
|
|
+ * using TypeB = opaque_typedef<...>;
|
|
|
+ *
|
|
|
+ * template <> template <>
|
|
|
+ * TypeA::opaque_typedef(TypeB const & other) { ... }
|
|
|
+ * \endcode
|
|
|
+ */
|
|
|
template <typename B, typename T, template <typename> class... S>
|
|
|
opaque_typedef(opaque_typedef<B, T, S...> const & other);
|
|
|
|
|
|
+ /**
|
|
|
+ * Explicit conversion for use in static_casts, or by invoking get()
|
|
|
+ * directly in your code.
|
|
|
+ */
|
|
|
Base const & get() const { return value_; }
|
|
|
explicit operator Base const &() const { return value_; }
|
|
|
};
|