Thread: Using arithmetic overloaded operators with built in types and objects

  1. #1
    Registered User
    Join Date
    Mar 2016
    Posts
    36

    Using arithmetic overloaded operators with built in types and objects

    Hello it's me again, I would like to know why do we have to use built in type of data as the driving operand in front of the + operator or any overloaded operator.
    For example this code doesn't compile, since in the main function double is right from the operator +

    Code:
    #include <iostream>
    using namespace std;
    
    
    class Transaction
    {
        friend double operator+(double, Transaction);
        private:
            int idNum;
            double payment;
        public:
            Transaction(double , int);
    };
    Transaction::Transaction(int id, double payment)
    {
        this->payment = payment;
        idNum = id;
    }
    double operator+(Transaction trans, double payment)
    {
        double sum;
        sum = payment + trans.payment;
        return sum;
    }
    int main(void)
    {
        Transaction transOne(15, 12.6);
        double sum = transOne + 5.2;
        cout << sum;
        return 0;
    }
    yet, If I put 5.2 left from the operator +, code executes correctly.

    Code:
    #include <iostream>
    using namespace std;
    
    
    class Transaction
    {
        friend double operator+(double, Transaction);
        private:
            int idNum;
            double payment;
        public:
            Transaction(double , int);
    };
    Transaction::Transaction(int id, double payment)
    {
        this->payment = payment;
        idNum = id;
    }
    double operator+(Transaction trans, double payment)
    {
        double sum;
        sum = payment + trans.payment;
        return sum;
    }
    int main(void)
    {
        Transaction transOne(15, 12.6);
        double sum = transOne + 5.2;
        cout << sum;
        return 0;
    }

  2. #2
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    It looks like your function signatures don't match.

  3. #3
    Registered User
    Join Date
    Mar 2016
    Posts
    36
    Quote Originally Posted by MutantJohn View Post
    It looks like your function signatures don't match.
    Could you please be more precise?

  4. #4
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    Oh, I simply meant that your friend function declaration and the function definition have different argument orders.

    Also, there's no need to use a friend function. You can just overload the operator in the class as well.

    Code:
    // Example program
    #include <cassert>
    #include <iostream>
    
    
    struct point
    {
      float x;
      float y;
      float z;
    
    
      auto operator+(float const n) const -> point
      {
        return {x + n, y + n, z + n};
      }
      
      auto operator==(point const& other) const -> bool
      {
          return x == other.x && y == other.y && z == other.z;
      }
    };
    
    
    int main(void)
    {
      assert((point{0, 1, 2} + 3 == point{3, 4, 5}));
      std::cout << "Tests passed!\n";
      return 0;
    }
    Last edited by MutantJohn; 10-07-2016 at 10:54 AM.

  5. #5
    Registered User
    Join Date
    Mar 2016
    Posts
    36
    • Quote Originally Posted by MutantJohn View Post
      Oh, I simply meant that your friend function declaration and the function definition have different argument orders.
    Quote Originally Posted by MutantJohn View Post

    Also, there's no need to use a friend function. You can just overload the operator in the class as well.

    Code:
    // Example program
    #include <cassert>
    #include <iostream>
    
    
    struct point
    {
      float x;
      float y;
      float z;
    
    
      auto operator+(float const n) const -> point
      {
        return {x + n, y + n, z + n};
      }
      
      auto operator==(point const& other) const -> bool
      {
          return x == other.x && y == other.y && z == other.z;
      }
    };
    
    
    int main(void)
    {
      assert((point{0, 1, 2} + 3 == point{3, 4, 5}));
      std::cout << "Tests passed!\n";
      return 0;
    }
    I haven't yet used casert library, I am learning by this book
    https://hienngong.files.wordpress.co...th_edition.pdf
    and I am currently on the Chapter 09. So I really don't know what these functions do (assert, auto).

  6. #6
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    I'm using C++11 and on syntax.

    `auto` tells the compiler that it should expected a "trailing return type". I prefer this syntax.

    `assert` is a function that checks a boolean condition. If it's true, it's all good. Otherwise, it crashes your code.

    And in my `operator+` function, I'm using another short-hand syntax by just returning an initializer list (I'm 90% confident the compiler treats that return statement as a list).

  7. #7
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    If you just want to be able to put the transaction on the left or right of operator + you can certainly do that, you just need to take the time to write it. A nice way of doing it is like this:
    Code:
    /* public */ double Transaction::Amount() const
    {
        return payment_;
    }
    
    /* public */ void Transaction::operator+= (double payment)
    {
        payment_ += payment;
    }
    
    // This is a non-member, non-friend implementation of operator+ and as such, 
    // Transaction:: in the function signature would be wrong.
    Transaction operator + (const Transaction& lhs, double amt)
    {
        Transaction dupe(lhs);
        dupe += amt;
        return dupe;
    }
    
    Transaction operator + (double amt, const Transaction& rhs)
    {
        // Addition is transitive here.
        return rhs + amt;
    }
    
    int main()
    {
        Transaction transOne(15, 12.60);
        transOne = 5.20 + transOne;
        cout << transOne.Amount() << endl;
    }
    Notice that the bulk of the work is done by operator +=, so if you ever change how you add to transactions, you need to change operator +=, and possibly Transaction operator + (const Transaction& lhs, double amt) as well, but that should be it.

    Creating function prototypes for both versions of operator+ would allow you to define them in any order as well, as long as one is written in terms of the other.
    Last edited by whiteflags; 10-07-2016 at 06:02 PM.

  8. #8
    Nasal Demon Xupicor's Avatar
    Join Date
    Sep 2010
    Location
    Poland
    Posts
    179
    @MutantJohn:
    `auto` tells the compiler that it should expected a "trailing return type". I prefer this syntax.
    auto tells the compiler that it should deduce the return type. It's obvious to "deduce" if you provide it directly by using the trailing return type syntax as you did - but the two are separate as auto can be used without the trailing return type syntax.

    Not in your example, though, because...
    (I'm 90% confident the compiler treats that return statement as a list)
    ...you return an initializer list, and thus the compiler needs some way of knowing what type of object to initialize using it. Again, you provide that type directly in the trailing return type syntax.

    I see little value in using auto in this instance if you still have to use the type name anyway, as is the case with operator+ here... I mean, each to his own, styles differ and such. I prefer the self-documenting nature of explicit return type when it comes to functions/methods, but having it by means of trailing syntax and auto in front... I guess it comes down as to which you prefer to read.

  9. #9
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    You know, I've never actually had much luck when trying to get deduced return types so the "auto" syntax is more naturally associated with trailing return syntax.

    I actually opted for trailing style and use it everywhere solely for the sake of consistency. One thing I like is that it hides longer return types a lot better and all your functions align if you have headers and source files. The fact that we can return that in class methods is really cool too! It's a super light and bare syntax. I think it's groovy.

  10. #10
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by MutantJohn View Post
    `assert` is a function that checks a boolean condition. If it's true, it's all good. Otherwise, it crashes your code.
    No, it raises an "assertion."

    And in my `operator+` function, I'm using another short-hand syntax by just returning an initializer list (I'm 90% confident the compiler treats that return statement as a list).
    The compiler deduces it to an initializer list, but the standard says it can implicitly convert an initializer list to whatever object you're returning by passing those parameters in the initializer list directly to the constructor, or in case of a "simple struct type", it initializes it using an aggregate initializer list, I think. Not sure about the standards terminology here. It's hard.

    Quote Originally Posted by Xupicor View Post
    auto tells the compiler that it should deduce the return type. It's obvious to "deduce" if you provide it directly by using the trailing return type syntax as you did
    No, mutantjohn is right in this case. "auto" used in this way tells the compiler to expect a trailing return type.

    This originally came from lambdas:

    Code:
    [capture list](argument list) -> return type { function body }
    The return type of lambdas is specified by "-> return type" after the function name as opposed to before the function name for normal functions, so the committee then thought, that for consistency's sake, they should do the same for functions, as well. But they couldn't just omit the return type as that would result in a parse error. Hence, they introduced "auto" as a way to tell the compiler "hey, the return type comes later."

    but the two are separate as auto can be used without the trailing return type syntax.
    And this was introduced in C++14. If you use auto and omit the trailing return type, you're telling the compiler "hey, deduce the return type for me."

    So, to clarify:
    MyReturnType MyFunction(...) (C++98)

    auto MyFunction(...) -> MyReturnType (C++11 and forward)
    Tells the compiler the return type comes AFTER the function name. Omitting the return type is an error. This is supported in C++14 and forward too, of course.

    auto MyFunction(...) (C++14 and forward)
    The return type is deduced based on what you return. If the compiler can't figure it out, it's a compile error.

    As for return type before or after function name, it's a matter of style. The obvious improvement of putting it after is that it's easier to spot the function name as opposed to its return type.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  11. #11
    Nasal Demon Xupicor's Avatar
    Join Date
    Sep 2010
    Location
    Poland
    Posts
    179
    I should have probably included a little backstory like you did, and specify I'm talking about the newest revision. So, for completeness sake, lets see how the standard changed when it comes to the "new" auto:

    C++98 had auto keyword work as storage class specifier (no longer valid usage since C++11), so that doesn't interest us.

    From C++11 draft N3242:
    7.1.6.4 auto specifier [dcl.spec.auto]
    1 The auto type-specifier signifies that the type of a variable being declared shall be deduced from its initializer or that a function declarator shall include a trailing-return-type.
    2 The auto type-specifier may appear with a function declarator with a trailing-return-type (8.3.5) in any context where such a declarator is valid.
    From C++14 draft N3797:
    7.1.6.4 auto specifier [dcl.spec.auto]
    1 The auto and decltype(auto) type-specifiers designate a placeholder type that will be replaced later, either by deduction from an initializer or by explicit specification with a trailing-return-type. The auto type-specifier is also used to signify that a lambda is a generic lambda.
    2 The placeholder type can appear with a function declarator in the decl-specifier-seq, type-specifier-seq, conversion-function-id, or trailing-return-type, in any context where such a declarator is valid. If the function declarator includes a trailing-return-type (8.3.5), that specifies the declared return type of the function.
    If the declared return type of the function contains a placeholder type, the return type of the function is deduced from return statements in the body of the function, if any.
    From C++17 draft N4582:
    7.1.6.4 auto specifier [dcl.spec.auto]1 The auto and decltype(auto) type-specifiers are used to designate a placeholder type that will be replaced later by deduction from an initializer. The auto type-specifier is also used to introduce a function type having a trailing-return-type or to signify that a lambda is a generic lambda.
    2 The placeholder type can appear with a function declarator in the decl-specifier-seq, type-specifier-seq, conversion-function-id, or trailing-return-type, in any context where such a declarator is valid. If the function declarator includes a trailing-return-type (8.3.5), that trailing-return-type specifies the declared return type of the function. Otherwise, the function declarator shall declare a function.
    If the declared return type of the function contains a placeholder type, the return type of the function is deduced from return statements in the body of the function, if any.
    The compiler deduces it to an initializer list, but the standard says it can implicitly convert an initializer list to whatever object you're returning by passing those parameters in the initializer list directly to the constructor, or in case of a "simple struct type", it initializes it using an aggregate initializer list, I think. Not sure about the standards terminology here. It's hard.
    Well, I'd say, correcting my wording in light of your input and of what the standard actually says, that the return type here isn't as much deduced as it is actually already known, since it was provided explicitly in the trailing-return-type. So the value returned from the function using the return statement - an initializer list in this case - if it's not already of the type needed (and it isn't) is going to go through the process of finding a proper conversion or in other words the compiler will try to initialize a new object of already known type point using returned value as the initializer, so more or less just like you said. Since in our case the type point is an aggregate, it can be initialized by an initializer list as per the usual rules. Again, pretty much exactly like you said.

    From C++14 draft N3797:
    8.5.1 Aggregates [dcl.init.aggr]
    1 An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).
    2 When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause.
    8.5 Initializers whole is kind of relevant, especially 8.5.4 List-initialization, 12.3 Conversions is a little further, also relevant. Anyone interested can get the nitty gritty there.

    So, holy mother of formal writing, did I just hijack the thread? Well, whiteflags answered the direct question, so I guess it's not that bad, still sorry about that. I'm just a little hungover (talk about understatements), so you'll have to excuse me if I end my ISO trips on the above. I know, you're sooo disappointed. Sorry again!


    Oh, and ZeroesAndOnes, don't mind the rambling about technicalities that aren't even connected to your question. If you still have questions about your original problem - don't be afraid to ask them. ; )

  12. #12
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    Sorry if the syntax was "jarring". It's a very JavaScript-y kind of syntax, imo. But I like it. C++11 and onwards has a really nice and new syntax that I think sort of revitalizes C++.

  13. #13
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    I dunno if I would call it a revitalization to put the return type at the end of the signature. Well, I prefer a discussion like this as opposed to you just writing as you like, and leaving us to figure out it's a C++14 feature. While I certainly understand the rationale of "lambdas do this, so everything function-like should do this," preferring it seems... against convention...? I'm not sold on it, I guess is what I'm saying; least of all for reasons like everything will be nicely aligned. I don't care.

    And as far as deducing the type all the time... well, have you ever seen If Google was a guy, and got to the part where the black dude asks pseudo questions to the search bar? In some contexts, type deduction is nice, and unavoidable, but if we use it constantly, I start to humanize the compiler. And we're treating the compiler like we treat Google guy.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. conflicting types for built-in function
    By BIOS in forum C Programming
    Replies: 5
    Last Post: 09-12-2011, 01:41 PM
  2. Can objects be used as built-ins?
    By BdON003 in forum C++ Programming
    Replies: 4
    Last Post: 09-10-2011, 12:38 PM
  3. Using overloaded operators
    By Niels_M in forum C++ Programming
    Replies: 2
    Last Post: 11-09-2010, 07:16 AM
  4. Overloaded operators
    By Kayoss in forum C++ Programming
    Replies: 6
    Last Post: 04-17-2006, 02:23 PM
  5. overloaded operators
    By ivandn in forum C++ Programming
    Replies: 2
    Last Post: 12-20-2001, 03:52 PM

Tags for this Thread