Can I create a new operator in C++ and how?

前端 未结 9 922
耶瑟儿~
耶瑟儿~ 2020-11-27 18:43

MATLAB arrays support matrix operations and element operations. For example, M*N and M.*N. This is a quite intuitive way to distinguish ‎the two di

相关标签:
9条回答
  • 2020-11-27 19:01

    BTW: I am seeking to answer the parts of this question as asked. I am also not seeking to replicate all the information in other worthy answers. The bounty seeks something different to the question as asked, so I am not responding to that.

    It is actually fairly simple to provide a matrix multiplication. Since I'm not proposing to describe data structures to represent a matrix and fully implement operations and validity checks on them, I'll just provide skeletons to illustrate.

    Example 1: operator*() as a member function

    class M   // a basic matrix class
    {
        public:
    
              // assume other constructors and members to set things up
           M operator*(const M &rhs) const;
    };
    
    M M::operator*(const M &rhs) const
    {
           //   implement checks on dimensions, throw an exception if invalid
    
           M result;
            //  implement the multiplication (typical iterations) and store results in result
           return result;
    }
    
    int main()
    {
         M a;
         M b;
            // set up elements of a and b as needed
         M c = a*b;    // this relies on M having appropriate constructor(s) to copy or move the result of a*b into c
    
         M d;
         d = a * b;    //  this relies on M having appropriate operator=() to assign d to the result of a*b
    }
    

    The above implements operator*() as a member function. So, functionally, c = a*b is equivalent to c = a.operator*(b). The const qualifiers represent the fact that a matrix multiplication a*b does not generally change a or b.

    Example 2: operator*() as a non-member function

    Now, operator*() can also be implemented as a non-member (optionally a friend), with a skeleton that looks like

    class M   // our basic matrix class, different operator *
    {
        public:
    
              // assume other constructors and members to set things up
           friend M operator*(const M &lhs, const M &rhs);
    };
    
    M operator*(const M &lhs, const M &rhs)
    {
           //   implement checks on dimensions, throw an exception if invalid
    
           M result;
            //  implement the multiplication (typical iterations) and store results in result
           return result;
    }
    
    //   same main() as before
    

    Note that, in this case, a*b is now equivalent to operator*(a, b).

    If you want to use both forms, care is needed to avoid ambiguity. If both forms of operator*() are provided they are both valid matches in a statement like c = a*b and the compiler has no means to choose one form over the other. The result is code not compiling.

    Example 3: overloading operator*()

    It is also possible to overload operator*() - for example, to multiply a matrix by a scalar.

    class M   // a basic matrix class
    {
        public:
    
              // assume other constructors and members to set things up
           M operator*(const M &rhs) const;    // as in first example
    
           M operator*(double scalar) const;    // member form
           friend M operator*(double scalar, const M &rhs);   // non-member form
    };
    
    M M::operator*(double scalar) const
    {
           M result;
            //  implement the multiplication (typical iterations) and store results in result
           return result;
    }
    
    M operator*(double scalar, const M &m)
    {
           M result;
            //  implement the multiplication (typical iterations) and store results in result
           return result;
    }
    
    int main()
    {
         M a;
         M b;
            // set up elements of a and b as needed
         M c = b * 2.0;    // uses the member form of operator*() above
    
         M d;
         d = 2.0*a;        //  uses the non-member form of operator*() above
    }
    

    In the above b*2.0 amounts to a call of b.operator*(2.0) and 2.0*a to a call of the non-member operator*(2.0, a). The member forms can only generally be used in expressions where the left hand operand is of type M. So 2.0*a will not work if only member forms of operator*() is provided.

    Discussion

    Apart from concerns of ambiguity above, there are other things to be aware of when overloading operators.

    • It is not possible to change precedence or associativity of operators from their specification in language rules. So, in the expression a+b*c, the * will always have higher precedence than the +. This is also the reason it is not a good idea to overload ^ for exponentiation in C++, since ^ has a lower precedence than + in C++ (being a bitwise operation on integral types). So a + b^c is actually equivalent in C++ to (a + b)^c, not to a + (b^c) (which anyone with basic knowledge of algebra would expect).
    • The language specifies a set of operators, and it is not possible to create new ones. For example, there is no ** in C++, such that a ** b raises a to the power of b (which other languages can do), and it is not possible to create one.
    • Not all operators can be overloaded.

    One of the operators that cannot be overloaded in C++ is .*. So it is not possible to use such an operator like you would in Matlab. I would generally suggest NOT trying to get the same effect using other operators, because the above constraints will affect that (and cause expressions to give counter-intuitive behaviour). Instead simply provide another named function to do the job. For example, as a member function

       class M
       {
           public:
             // other stuff
    
              M ElementWiseProduct(const M &) const;
       };
    
    0 讨论(0)
  • 2020-11-27 19:04

    No, unfortunately you cannot define new operators—you can only overload existing operators (with a few important exceptions, such as operator.). Even then, it's typically only a good idea to overload operators for types which have very clear and uncontroversial existing semantics for a given operator—for instance, any type that behaves as a number is a good candidate for overloading the arithmetic and comparison operators, but you should make sure that operator+ doesn't, say, subtract two numbers.

    0 讨论(0)
  • 2020-11-27 19:07

    In C++, there's a list of predefined operators, most of which are overloadable (.* is not). Additionally, any name can be used as an operator like:

    #include <iostream>
    
    // generic LHSlt holder
    template<typename LHS, typename OP>
    struct LHSlt {
        LHS lhs_;
    };
    
    // declare myop as an operator-like construct
    enum { myop };
    
    // parse 'lhs <myop' into LHSlt
    template<typename LHS>
    LHSlt<LHS, decltype(myop)> operator<(const LHS& lhs, decltype(myop))
    {
        return { lhs };
    }
    
    // declare (int <myop> int) -> int
    int operator>(LHSlt<int, decltype(myop)> lhsof, int rhs)
    {
        int& lhs = lhsof.lhs_;
        // here comes your actual implementation
        return (lhs + rhs) * (lhs - rhs);
    }
    
    // strictly optional
    #define MYOP <myop>
    
    int main() {
        std::cout << (5 <myop> 2) << ' ' << (5 MYOP 2);
    }
    

    Disclaimer: This, strictly speaking, gets translated to (5 < myop) > 2, which is LHSlt<int, decltype(myop)>(5) > 2. Thus it's not a new 'operator', in C++-terms, but it's used exactly the same way, even in terms of ADL. Also, if type is large, you probably want to store const T&.

    Note that you can do this with any binary operator that can be defined external to the class; precedence is based on the precedence of the two sides (< and >). Thus you can have e.g. *myop*, +myop+, <<myop>>, <myop>, |myop| in this order of precedence.

    If you want right-associativity, it gets a bit more tricky. You'll need both of a RHS-holder and LHS-holder (the latter being LHSlt here) and use surrounding operators such that the right one has higher precedence than the left one, e.g. a |myop> b |myop>c is a |myop> (b |myop> c). Then you need the function for both your type and your holder type as the lhs.

    0 讨论(0)
提交回复
热议问题