Extension methods in c++

后端 未结 6 1506
我在风中等你
我在风中等你 2020-11-27 16:38

I was searching for an implementation of extension methods in c++ and came upon this comp.std.c++ discussion which mentions that polymorphic_map can be used to

相关标签:
6条回答
  • 2020-11-27 17:20

    This is the closest thing that I have ever seen to extension methods in C++. Personally i like the way it can be used, and possibly this it the closest we can get to extension methods in this language. But there are some disadvantages:

    • It may be complicated to implement
    • Operator precedence may be not that nice some times, this may cause surprises

    A solution:

    #include <iostream>
    
    using namespace std;
    
    
    class regular_class {
    
        public:
    
            void simple_method(void) const {
                cout << "simple_method called." << endl;
            }
    
    };
    
    
    class ext_method {
    
        private:
    
            // arguments of the extension method
            int x_;
    
        public:
    
            // arguments get initialized here
            ext_method(int x) : x_(x) {
    
            }
    
    
            // just a dummy overload to return a reference to itself
            ext_method& operator-(void) {
                return *this;
            }
    
    
            // extension method body is implemented here. The return type of this op. overload
            //    should be the return type of the extension method
            friend const regular_class& operator<(const regular_class& obj, const ext_method& mthd) {
    
                cout << "Extension method called with: " << mthd.x_ << " on " << &obj << endl;
                return obj;
            }
    };
    
    
    int main()
    { 
        regular_class obj;
        cout << "regular_class object at: " << &obj << endl;
        obj.simple_method();
        obj<-ext_method(3)<-ext_method(8);
        return 0;
    }
    

    This is not my personal invention, recently a friend of mine mailed it to me, he said he got it from a university mailing list.

    0 讨论(0)
  • 2020-11-27 17:22

    Different languages approach development in different ways. In particular C# and Java have a strong point of view with respect to OO that leads to everything is an object mindset (C# is a little more lax here). In that approach, extension methods provide a simple way of extending an existing object or interface to add new features.

    There are no extension methods in C++, nor are they needed. When developing C++, forget the everything is an object paradigm --which, by the way, is false even in Java/C# [*]. A different mindset is taken in C++, there are objects, and the objects have operations that are inherently part of the object, but there are also other operations that form part of the interface and need not be part of the class. A must read by Herb Sutter is What's In a Class?, where the author defends (and I agree) that you can easily extend any given class with simple free functions.

    As a particular simple example, the standard templated class basic_ostream has a few member methods to dump the contents of some primitive types, and then it is enhanced with (also templated) free functions that extend that functionality to other types by using the existing public interface. For example, std::cout << 1; is implemented as a member function, while std::cout << "Hi"; is a free function implemented in terms of other more basic members.

    Extensibility in C++ is achieved by means of free functions, not by ways of adding new methods to existing objects.

    [*] Everything is not an object.

    In a given domain will contain a set of actual objects that can be modeled and operations that can be applied to them, in some cases those operations will be part of the object, but in some other cases they will not. In particular you will find utility classes in the languages that claim that everything is an object and those utility classes are nothing but a layer trying to hide the fact that those methods don't belong to any particular object.

    Even some operations that are implemented as member functions are not really operations on the object. Consider addition for a Complex number class, how is sum (or +) more of an operation on the first argument than the second? Why a.sum(b); or b.sum(a), should it not be sum( a, b )?

    Forcing the operations to be member methods actually produces weird effects --but we are just used to them: a.equals(b); and b.equals(a); might have completely different results even if the implementation of equals is fully symmetric. (Consider what happens when either a or b is a null pointer)

    0 讨论(0)
  • 2020-11-27 17:23

    To elaborate more on @Akira answer, operator| can be used to extend existing classes with functions that take parameters too. Here an example that I'm using to extend Xerces XML library with find functionalities that can be easily concatenated:

    #pragma once
    
    #include <string>
    #include <stdexcept>
    
    #include <xercesc/dom/DOMElement.hpp>
    
    #define _U16C // macro that converts string to char16_t array
    
    XERCES_CPP_NAMESPACE_BEGIN
        struct FindFirst
        {
            FindFirst(const std::string& name);
            DOMElement * operator()(const DOMElement &el) const;
            DOMElement * operator()(const DOMElement *el) const;
        private:
            std::string m_name;
        };
    
        struct FindFirstExisting
        {
            FindFirstExisting(const std::string& name);
            DOMElement & operator()(const DOMElement &el) const;
        private:
            std::string m_name;
        };
    
        inline DOMElement & operator|(const DOMElement &el, const FindFirstExisting &f)
        {
            return f(el);
        }
    
        inline DOMElement * operator|(const DOMElement &el, const FindFirst &f)
        {
            return f(el);
        }
    
        inline DOMElement * operator|(const DOMElement *el, const FindFirst &f)
        {
            return f(el);
        }
    
        inline FindFirst::FindFirst(const std::string & name)
            : m_name(name)
        {
        }
    
        inline DOMElement * FindFirst::operator()(const DOMElement &el) const
        {
            auto list = el.getElementsByTagName(_U16C(m_name));
            if (list->getLength() == 0)
                return nullptr;
    
            return static_cast<DOMElement *>(list->item(0));
        }
    
        inline DOMElement * FindFirst::operator()(const DOMElement *el) const
        {
            if (el == nullptr)
                return nullptr;
    
            auto list = el->getElementsByTagName(_U16C(m_name));
            if (list->getLength() == 0)
                return nullptr;
    
            return static_cast<DOMElement *>(list->item(0));
        }
    
        inline FindFirstExisting::FindFirstExisting(const std::string & name)
            : m_name(name)
        {
        }
    
        inline DOMElement & FindFirstExisting::operator()(const DOMElement & el) const
        {
            auto list = el.getElementsByTagName(_U16C(m_name));
            if (list->getLength() == 0)
                throw runtime_error(string("Missing element with name ") + m_name);
    
            return static_cast<DOMElement &>(*list->item(0));
        }
    
    XERCES_CPP_NAMESPACE_END
    

    It can be used this way:

    auto packetRate = *elementRoot | FindFirst("Header") | FindFirst("PacketRate");
    auto &decrypted = *elementRoot | FindFirstExisting("Header") | FindFirstExisting("Decrypted");
    
    0 讨论(0)
  • 2020-11-27 17:27

    The short answer is that you cannot do that. The long answer is that you can simulate it, but be aware that you'll have to create a lot of code as workaround (actually, I don't think there is an elegant solution).

    In the discussion, a very complex workaround is provided using operator- (which is a bad idea, in my opinion). I guess that the solution provided in the dead link was more o less similar (since it was based on operator|).

    This is based in the capability of being able to do more or less the same thing as an extension method with operators. For example, if you want to overload the ostream's operator<< for your new class Foo, you could do:

    class Foo {
        friend ostream &operator<<(ostream &o, const Foo &foo);
        // more things...
    };
    
    ostream &operator<<(ostream &o, const Foo &foo)
    {
      // write foo's info to o
    }
    

    As I said, this is the only similar mechanism availabe in C++ for extension methods. If you can naturally translate your function to an overloaded operator, then it is fine. The only other possibility is to artificially overload an operator that has nothing to do with your objective, but this is going to make you write very confusing code.

    The most similar approach I can think of would mean to create an extension class and create your new methods there. Unfortunately, this means that you'll need to "adapt" your objects:

    class stringext {
    public:
        stringext(std::string &s) : str( &s )
            {}
        string trim()
            {  ...; return *str; }
    private:
        string * str;
    };
    

    And then, when you want to do that things:

    void fie(string &str)
    {
        // ...
        cout << stringext( str ).trim() << endl;
    }
    

    As said, this is not perfect, and I don't think that kind of perfect solution exists. Sorry.

    0 讨论(0)
  • 2020-11-27 17:27

    You can enable kinda extension methods for your own class/struct or for some specific type in some scope. See rough solution below.

    class Extensible
    {
    public:
        template<class TRes, class T, class... Args>
        std::function<TRes(Args...)> operator|
            (std::function<TRes(T&, Args...)>& extension)
        {
            return [this, &extension](Args... args) -> TRes
            {
                return extension(*static_cast<T*>(this), std::forward<Args>(args)...);
            };
        }
    };
    

    Then inherit your class from this and use like

    class SomeExtensible : public Extensible { /*...*/ };
    std::function<int(SomeExtensible&, int)> fn;
    SomeExtensible se;
    int i = (se | fn)(4);
    

    Or you can declare this operator in cpp file or namespace.

    //for std::string, for example
    template<class TRes, class... Args>
    std::function<TRes(Args...)> operator|
        (std::string& s, std::function<TRes(std::string&, Args...)>& extension)
    {
        return [&s, &extension](Args... args) -> TRes
        {
            return extension(s, std::forward<Args>(args)...);
        };
    }
    
    std::string s = "newStr";
    std::function<std::string(std::string&)> init = [](std::string& s) {
        return s = "initialized";
    };
    (s | init)();
    

    Or even wrap it in macro (I know, it's generally bad idea, nevertheless you can):

    #define ENABLE_EXTENSIONS_FOR(x) \
    template<class TRes, class... Args> \
    std::function<TRes(Args...)> operator| (x s, std::function<TRes(x, Args...)>& extension) \
    { \
        return [&s, &extension](Args... args) -> TRes \
        { \
            return extension(s, std::forward<Args>(args)...); \
        }; \
    }
    
    ENABLE_EXTENSIONS_FOR(std::vector<int>&);
    
    0 讨论(0)
  • 2020-11-27 17:32

    Boost Range Library's approach use operator|().

    r | filtered(p);
    

    I can write trim for string as follows in the same way, too.

    #include <string>
    
    namespace string_extension {
    
    struct trim_t {
        std::string operator()(const std::string& s) const
        {
            ...
            return s;
        }
    };
    
    const trim_t trim = {};
    
    std::string operator|(const std::string& s, trim_t f)
    {
        return f(s);
    }
    
    } // namespace string_extension
    
    int main()
    {
        const std::string s = "  abc  ";
    
        const std::string result = s | string_extension::trim;
    }
    
    0 讨论(0)
提交回复
热议问题