// https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#t5-combine-generic-and-oo-techniques-to-amplify-their-strengths-not-their-costs // https://www.youtube.com/watch?v=QGcVXgEVMJg // https://www.youtube.com/watch?v=bIhUE5uUFOA // https://www.youtube.com/watch?v=4eeESJQk-mw&t=23m26s // https://www.youtube.com/watch?v=gVGtNFg4ay0 // https://www.youtube.com/watch?v=OtU51Ytfe04 // https://www.youtube.com/watch?v=8c6BAQcYF_E // https://wiki.c2.com/?ExternalPolymorphism // https://www.dre.vanderbilt.edu/~schmidt/PDF/C++-EP.pdf #include <iostream> #include <iomanip> #include <string> #include <vector> #include <unordered_map> #include <memory> #include <algorithm> using namespace std::string_literals; // String std::ostream & operator<<(std::ostream & ostream, std::string const & string) { ostream << std::quoted(string); return ostream; } // Value class Value { public: template<typename Data> Value(Data data) : concept_{new Model<Data>(std::move(data))} {} Value(Value const & other) : concept_(other.concept_->clone()) {} Value & operator=(Value const & other) { concept_.reset(other.concept_->clone()); return *this; } Value(Value && other) = default; Value & operator=(Value && other) = default; template<typename Data> Data const * get() const { if(auto model = dynamic_cast<Model<Data> *>(concept_.get())) return &model->data; return nullptr; } template<typename Data> Data * get() { // return const_cast<Data *>( // const_cast<Value const *>(this)->get<Data>() // ); return std::as_const(*this).get<Data>(); } friend std::ostream & operator<<( std::ostream & ostream, Value const & value ) { value.concept_->print(ostream); return ostream; } private: struct Concept { virtual ~Concept() = default; virtual Concept * clone() const = 0; virtual void print(std::ostream & ostream) const = 0; }; template<typename Data> struct Model : Concept { Model(Data data) : data{std::move(data)} {} Concept * clone() const override { return new Model<Data>(*this); } void print(std::ostream & ostream) const override { ostream << data; } Data data; }; std::unique_ptr<Concept> concept_; }; // List using List = std::vector<Value>; std::ostream & operator<<(std::ostream & ostream, List const & list) { ostream << "[ "; for (auto const & elem : list) ostream << elem << " "; ostream << "]"; return ostream; } // Operation class Operation { public: virtual ~Operation() = default; virtual void operator()(List & value) = 0; }; class DuplicateOperation : public Operation { public: void operator()(List & list) override { list.reserve(2 * list.size()); std::copy(list.begin(), list.end(), std::back_inserter(list)); } }; template<typename Predicate> class FilterOperation : Operation { public: FilterOperation(Predicate predicate) : predicate_(std::move(predicate)) {} void operator()(List & list) override { list.erase( std::remove_if( list.begin(), list.end(), [this](Value const & value) { return !predicate_(value); } ), list.end() ); } private: Predicate predicate_; }; class ReplaceOperation : Operation { public: using Map = std::unordered_map<std::string, std::string>; ReplaceOperation(Map map) : map_(std::move(map)) {} void operator()(List & list) override { for (auto & elem : list) if (auto * string = elem.get<std::string>()) if (auto it = map_.find(*string); it != map_.end()) *string = it->second; } private: Map map_; }; /* Testcase 1: [ 0 "Hello" 1 "World!" ] After filtering out only strings: [ "Hello" "World!" ] After replace text: [ "Howdy" "World!" ] Testcase 2: [ 1 -5 2 "-3" ] After duplication: [ 1 -5 2 "-3" 1 -5 2 "-3" ] After filtering out positive integers: [ 1 2 "-3" 1 2 "-3" ] Testcase 3: [ "A" [ "B" "C" ] "D" ] After duplication: [ "A" [ "B" "C" ] "D" "A" [ "B" "C" ] "D" ] Testcase 4: [ 3.14 ] After assignment: [ "Something else" ] */ bool is_string(Value const & value) { return value.get<std::string>(); } bool is_nonint_or_nonnegative(Value const & value) { if (auto * i = value.get<int>()) return *i >= 0; return true; } int main() { { auto list = List{0, "Hello"s, 1, "World!"s}; std::cout << "Testcase 1:" << std::endl; std::cout << list << std::endl; std::cout << "After filtering out only strings:" << std::endl; auto op1 = FilterOperation{is_string}; op1(list); std::cout << list << std::endl; std::cout << "After replace text:" << std::endl; auto op2 = ReplaceOperation{{ {"Hello", "Howdy"}, }}; op2(list); std::cout << list << std::endl; } std::cout << std::endl; { auto list = List{1, -5, 2, "-3"s}; std::cout << "Testcase 2:" << std::endl; std::cout << list << std::endl; std::cout << "After duplication:" << std::endl; auto op1 = DuplicateOperation{}; op1(list); std::cout << list << std::endl; std::cout << "After filtering out positive integers:" << std::endl; auto op2 = FilterOperation{is_nonint_or_nonnegative}; op2(list); std::cout << list << std::endl; } std::cout << std::endl; { auto list = List{"A"s, List{"B"s, "C"s}, "D"s}; std::cout << "Testcase 3:" << std::endl; std::cout << list << std::endl; std::cout << "After duplication:" << std::endl; auto op1 = DuplicateOperation{}; op1(list); std::cout << list << std::endl; } std::cout << std::endl; { auto list = List{3.14}; std::cout << "Testcase 4:" << std::endl; std::cout << list << std::endl; std::cout << "After assignment:" << std::endl; list[0] = "And now for something completely different"s; std::cout << list << std::endl; } }