#include #include #include #include #include template struct SequenceTraits { // By ensuring that this is nonsense for GridKind, we know that this // version of the template can never apply, and a specialization // must be used. using Unresolved = typename SequenceKind::UnspecializedTraitError; // must publish // types // the kind itself for convenience // position type // value type // behaviors // get first // get next // get value from position }; // NOTE: We *could* use inheritance and override checking to ensure that // appropriate methods and types are exposed. This essentially uses CRTP // to hoist appropriate information from the derived implementations of // the trait, but it is a bit of an awkward use of inheritance. // // Notice that as written below, it uses the CRTP to ensure compile time // resolution of the underlying types. This is also a bit awkward. Ideally, // the compile time polymorphism features of a language would allow us to // express a required interface without needing to resort to inheritance. // // There is a substantial amount of boilerplate using this approach to // ensure API consistency. Demonstrating this approach is not the same as // advocating for it. // template struct SequenceTraitConstraints { public: using Kind = SequenceKind; using Value = ValueKind; using Position = PositionKind; std::optional getFirst(const Kind& sequence) { return getTraits().getFirstImpl(sequence); } std::optional getNext(const Kind& sequence, Position position) { return getTraits().getNextImpl(sequence, position); } Value getValue(Position position) { return getTraits().getValueImpl(position); } private: TraitName& getTraits() { return *static_cast(this); } virtual std::optional getFirstImpl(const Kind& sequence) = 0; virtual std::optional getNextImpl(const Kind& sequence, Position position) = 0; virtual Value getValueImpl(Position position) = 0; }; ///////////////////////////////////////////////////////////////////////////// // We can define behaviors that are independent of the specific structures // in question but that make use of information about them. This is done in // part through specialization of the trait information for the types of interest. ///////////////////////////////////////////////////////////////////////////// template> bool isIncreasing(const SequenceKind& sequence) { Traits traits; using Position = typename Traits::Position; std::optional previous = std::nullopt; std::optional next = traits.getFirst(sequence); while (next) { if (previous && traits.getValue(*next) <= traits.getValue(*previous)) { return false; } previous = next; next = traits.getNext(sequence, *next); } return true; } template> void printSequence(const SequenceKind& sequence) { Traits traits; using Position = typename Traits::Position; std::optional next = traits.getFirst(sequence); while (next) { std::cout << traits.getValue(*next) << " "; next = traits.getNext(sequence, *next); } std::cout << "\n"; } ///////////////////////////////////////////////////////////////////////////// // In order to make these work, we need to define the types that we care about // along with extra trait information to glue the trait driven code to the type ///////////////////////////////////////////////////////////////////////////// template