#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 }; ///////////////////////////////////////////////////////////////////////////// // 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) { 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> bool printSequence(const SequenceKind& sequence) { 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"; return true; } ///////////////////////////////////////////////////////////////////////////// // 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 struct SequenceTraits> { using Kind = std::vector; using Value = T; using Position = typename Kind::const_iterator; static std::optional getFirst(const Kind& sequence) { if (!sequence.empty()) { return sequence.begin(); } else { return {}; } } static std::optional getNext(const Kind& sequence, Position position) { auto next = ++position; if (next != sequence.end()) { return next; } else { return {}; } } static Value getValue(Position position) { return *position; } }; struct Hailstone { int start = 10; }; template<> struct SequenceTraits { using Kind = Hailstone; using Value = int; using Position = Value; static std::optional getFirst(const Kind& sequence) { return sequence.start; } static std::optional getNext(const Kind& sequence, Position position) { if (position == 1) { return {}; } else if (position % 2 == 0) { return position / 2; } else { return 3 * position + 1; } } static Value getValue(Position position) { return position; } }; struct BoundedHailstone { using Original = SequenceTraits; using Kind = typename Original::Kind; using Value = typename Original::Value; using Position = std::pair; constexpr static int BOUND = 10; static std::optional getFirst(const Kind& sequence) { return std::pair{BOUND, *Original::getFirst(sequence)}; } static std::optional getNext(const Kind& sequence, Position position) { if (position.first == 0) { return {}; } auto next = Original::getNext(sequence, position.second); if (!next) { return {}; } else { return std::pair{position.first - 1, *next}; } } static Value getValue(Position position) { return position.second; } }; ///////////////////////////////////////////////////////////////////////////// // Once we have done so, *using* the APIs actually becomes clear, maintainable, // and very expressive. There is definitely a trade off, though. This accumulates // one form of complexity in order to reduce another. ///////////////////////////////////////////////////////////////////////////// int main() { using namespace std::string_literals; std::vector fibs = { 0, 1, 1, 2, 3, 5, 8, 13 }; std::cout << "vector fibonacci " << isIncreasing(fibs) << "\n"; std::vector odds = { 1, 3, 5, 7, 9, 11, 13 }; std::cout << "vector odds " << isIncreasing(odds) << "\n"; std::vector empty = { }; std::cout << "vector empty " << isIncreasing(empty) << "\n"; std::vector justOne = { "hello"s }; std::cout << "vector empty " << isIncreasing(justOne) << "\n"; Hailstone hail7{7}; std::cout << "Hailstones 7 " << isIncreasing(hail7) << "\n"; printSequence(fibs); printSequence(hail7); printSequence(hail7); std::list listodds = { 1, 3, 5, 7, 9, 11, 13 }; std::cout << "list odds " << isIncreasing(listodds) << "\n"; return 0; } // TODO: // Discuss in class about safety objectives. // Discuss CRTP, concepts,