Return by value -- primitive type vs class type

Discussion in 'C++' started by DaKoadMunky, May 13, 2004.

  1. DaKoadMunky

    DaKoadMunky Guest

    Please consider the following...

    <CODE>

    #include <string>
    using namespace std;

    typedef int PrimitiveType;

    typedef string ClassType;

    PrimitiveType ReturnPrimitiveType()
    {
    PrimitiveType result;
    return result;
    }

    ClassType ReturnClassType()
    {
    ClassType result;
    return result;
    }

    int main()
    {
    PrimitiveType pt;
    ClassType ct;

    ReturnPrimitiveType() = pt; //ERROR!

    ReturnClassType() = ct; //OKAY!

    return 0;
    }

    </CODE>

    The first function call in main() generates the error "left operand must be
    l-value."

    The second function call in main() does not.

    Both functions return their results by value. The only difference between the
    functions is that the first returns a primitive type and the second returns a
    class type.

    Both are obviously temporary objects, but my compiler seems to consider the
    temporary of class type to be an l-value.

    The primitive return type seems implicitly const whereas with the class type
    return type I need to explicitly specify const.

    Is that the correct behavior?

    Why allow modification of the class type temporary but disallow it for the
    primitive temporary?

    It is not important to some specific problem I have, I just like knowing why
    languages work the way they do.

    Regards,
    Brian
    DaKoadMunky, May 13, 2004
    #1
    1. Advertising

  2. DaKoadMunky

    Leor Zolman Guest

    On 13 May 2004 19:33:37 GMT, (DaKoadMunky) wrote:

    >Please consider the following...
    >
    ><CODE>
    >
    >#include <string>
    >using namespace std;
    >
    >typedef int PrimitiveType;
    >
    >typedef string ClassType;
    >
    >PrimitiveType ReturnPrimitiveType()
    >{
    > PrimitiveType result;
    > return result;
    >}
    >
    >ClassType ReturnClassType()
    >{
    > ClassType result;
    > return result;
    >}
    >
    >int main()
    >{
    > PrimitiveType pt;
    > ClassType ct;
    >
    > ReturnPrimitiveType() = pt; //ERROR!
    >
    > ReturnClassType() = ct; //OKAY!
    >
    > return 0;
    >}
    >
    ></CODE>
    >
    >The first function call in main() generates the error "left operand must be
    >l-value."
    >
    >The second function call in main() does not.
    >
    >Both functions return their results by value. The only difference between the
    >functions is that the first returns a primitive type and the second returns a
    >class type.
    >
    >Both are obviously temporary objects, but my compiler seems to consider the
    >temporary of class type to be an l-value.


    Not an lvalue - something else. Not sure what the term is, but here's what
    the Standard says (3.10/2):

    "An lvalue refers to an object or function. Some rvalue
    expressions--those of class or cv-qualified class type--also refer to
    objects. [47]

    and footnote 47 says:

    "Expressions such as invocations of constructors and of functions that
    return a class type refer to objects, and the implementation can invoke
    a member function upon such objects, but the expressions are not
    lvalues."


    >The primitive return type seems implicitly const whereas with the class type
    >return type I need to explicitly specify const.
    >
    >Is that the correct behavior?


    Evidently so.

    >
    >Why allow modification of the class type temporary but disallow it for the
    >primitive temporary?


    Well, it would after all convenient to be able to do stuff like:

    getApp().run();

    (although getApp()->run() might be more intuitive).

    Or how about:

    cout << cstr_to_string_with_munging(someCstr).insert(0, '*') << endl;

    or even (according to footnote 47):

    cout << string("foobar").insert(0, '*');

    Since unnamed object return values have a location associated with them,
    and that location may be used as a "this" pointer to member function calls,
    why not allow it? OTOH, unnamed primitive return values don't have
    addresses, and thus it wouldn't make sense to allow operations that modify
    such objects.

    >
    >It is not important to some specific problem I have, I just like knowing why
    >languages work the way they do.


    So do I ;-)
    -leor

    >
    >Regards,
    >Brian


    --
    Leor Zolman --- BD Software --- www.bdsoft.com
    On-Site Training in C/C++, Java, Perl and Unix
    C++ users: download BD Software's free STL Error Message Decryptor at:
    www.bdsoft.com/tools/stlfilt.html
    Leor Zolman, May 13, 2004
    #2
    1. Advertising

  3. DaKoadMunky

    DaKoadMunky Guest

    >Since unnamed object return values have a location associated with them,
    >and that location may be used as a "this" pointer to member function calls,
    >why not allow it?


    I thought maybe by default that *this for an unnamed object would be const,
    allowing reading of the object but not writing it. This seems more like the
    "do as the ints" do approach I have heard advocated at times.

    >OTOH, unnamed primitive return values don't have addresses


    Sounds like my parents. When I ask to come visit they claim they don't have an
    address.

    Seriously though, unless the return value is a value that can be fixed at
    compile time the return value has to have a location that can be written to!
    On my implementation it seems to use registers, but that is a location
    nonetheless.

    Thanks for your time.
    DaKoadMunky, May 14, 2004
    #3
  4. DaKoadMunky

    Howard Guest

    "DaKoadMunky" <> wrote in message
    news:...
    > >Since unnamed object return values have a location associated with them,
    > >and that location may be used as a "this" pointer to member function

    calls,
    > >why not allow it?

    >
    > I thought maybe by default that *this for an unnamed object would be

    const,
    > allowing reading of the object but not writing it. This seems more like

    the
    > "do as the ints" do approach I have heard advocated at times.


    What's constant is the return value, which for an object is really a
    reference to data stored elsewhere. So, although the address of that
    unnamed object cannot be changed, any of the members of the object it refers
    to *can* be changed, in this case via the assignment operator (a function
    call). On the other hand, an assignment to an integer (or other primitive
    type) return value actually attempts to change the return value itself,
    which is not allowed.

    >
    > Seriously though, unless the return value is a value that can be fixed at
    > compile time the return value has to have a location that can be written

    to!
    > On my implementation it seems to use registers, but that is a location
    > nonetheless.


    It's a location, but it's not writeable in this sense. For an object, it
    refers to data stored elsewhere. For a primitive type, it *is* the data.
    Thus the difference in allowable behavior.

    -Howard
    Howard, May 14, 2004
    #4
  5. DaKoadMunky

    Leor Zolman Guest

    On Fri, 14 May 2004 14:47:01 GMT, "Howard" <> wrote:

    >
    >"DaKoadMunky" <> wrote in message
    >news:...
    >> >Since unnamed object return values have a location associated with them,
    >> >and that location may be used as a "this" pointer to member function

    >calls,
    >> >why not allow it?

    >>
    >> I thought maybe by default that *this for an unnamed object would be

    >const,
    >> allowing reading of the object but not writing it. This seems more like

    >the
    >> "do as the ints" do approach I have heard advocated at times.

    >
    >What's constant is the return value, which for an object is really a
    >reference to data stored elsewhere.


    Are you talking about how you think the compiler is (internally)
    implementing return-by-value semantics for objects, or are you referring to
    the case where the return type is actually declared as a reference? If the
    latter, OK; if the former, however, I say "not necessarily" (and for all I
    know, that may even be the case for the latter). Consider this program:

    //
    // smallobj.cpp:
    //

    #include <iostream>
    using namespace std;

    class small {
    public:
    small(int n) : value(n) {}
    int getValue() const { return value; }
    int operator++() { return ++value; }
    friend ostream &operator<<(ostream &os, const small &s)
    {
    return os << s.value;
    }

    private:
    int value;
    };

    small makeSmall(int n)
    {
    return small(n);
    }

    int main()
    {
    cout << ++makeSmall(10) << endl;
    return 0;
    }


    Output: 11

    Are there actually any guarantees that the "value" coming back from
    makeSmall() isn't in a register? I don't think so. If that's how the code
    generated lays it out, so much the better.
    -leor

    --
    Leor Zolman --- BD Software --- www.bdsoft.com
    On-Site Training in C/C++, Java, Perl and Unix
    C++ users: download BD Software's free STL Error Message Decryptor at:
    www.bdsoft.com/tools/stlfilt.html
    Leor Zolman, May 14, 2004
    #5
  6. DaKoadMunky

    Howard Guest

    "Leor Zolman" <> wrote in message
    news:...
    > On Fri, 14 May 2004 14:47:01 GMT, "Howard" <> wrote:
    >
    > >
    > >"DaKoadMunky" <> wrote in message
    > >news:...
    > >> >Since unnamed object return values have a location associated with

    them,
    > >> >and that location may be used as a "this" pointer to member function

    > >calls,
    > >> >why not allow it?
    > >>
    > >> I thought maybe by default that *this for an unnamed object would be

    > >const,
    > >> allowing reading of the object but not writing it. This seems more

    like
    > >the
    > >> "do as the ints" do approach I have heard advocated at times.

    > >
    > >What's constant is the return value, which for an object is really a
    > >reference to data stored elsewhere.

    >
    > Are you talking about how you think the compiler is (internally)
    > implementing return-by-value semantics for objects, or are you referring

    to
    > the case where the return type is actually declared as a reference? If the
    > latter, OK; if the former, however, I say "not necessarily" (and for all I
    > know, that may even be the case for the latter). Consider this program:
    >


    I'm not really describing either a reference (or a const) as such, just
    trying to point out that when an object is returned, regardless of how it is
    actually implememented (e.g., in a register), you can't *change* that value,
    any more than you can change it if it's a primitive type. But you *can*
    access its members. What I was trying to say, I guess, was that the object
    is not actually sitting in that register or unnamed location, because it is
    (at least potentially) too big. Rather, the compiler places an address
    there (wherever *there* is), and that address points to the actual data.
    This is true of return semantics as well as parameters passed by value: when
    passing an object by value, the address of the object is actually passed,
    not the contents of the object. That fact is what enables you to make use
    of the address to call functions or access members of the object.

    -Howard


    >
    Howard, May 14, 2004
    #6
  7. DaKoadMunky

    Leor Zolman Guest

    On Fri, 14 May 2004 17:58:30 GMT, "Howard" <> wrote:

    >
    >"Leor Zolman" <> wrote in message
    >news:...


    >> Are you talking about how you think the compiler is (internally)
    >> implementing return-by-value semantics for objects, or are you referring

    >to
    >> the case where the return type is actually declared as a reference? If the
    >> latter, OK; if the former, however, I say "not necessarily" (and for all I
    >> know, that may even be the case for the latter). Consider this program:
    >>

    >
    >I'm not really describing either a reference (or a const) as such, just
    >trying to point out that when an object is returned, regardless of how it is
    >actually implememented (e.g., in a register), you can't *change* that value,
    >any more than you can change it if it's a primitive type.


    Did you take a look at that program I posted? What is the line:

    cout << ++makeSmall(10) << endl;

    doing if not "changing the value" of the object returned by makeSmall() ?

    You wouldn't have been able to do that if the return value was a primitive
    type, but you /can/ do it with a user-defined type.

    > But you *can*
    >access its members. What I was trying to say, I guess, was that the object
    >is not actually sitting in that register or unnamed location, because it is
    >(at least potentially) too big. Rather, the compiler places an address
    >there (wherever *there* is), and that address points to the actual data.
    >This is true of return semantics as well as parameters passed by value: when
    >passing an object by value, the address of the object is actually passed,
    >not the contents of the object. That fact is what enables you to make use
    >of the address to call functions or access members of the object.


    And what I was trying to say is you can't say any of that.
    -leor

    >
    >-Howard
    >
    >
    >>

    >


    --
    Leor Zolman --- BD Software --- www.bdsoft.com
    On-Site Training in C/C++, Java, Perl and Unix
    C++ users: download BD Software's free STL Error Message Decryptor at:
    www.bdsoft.com/tools/stlfilt.html
    Leor Zolman, May 14, 2004
    #7
  8. DaKoadMunky

    Howard Guest

    "Leor Zolman" <> wrote in message
    news:...
    > On Fri, 14 May 2004 17:58:30 GMT, "Howard" <> wrote:
    >
    > >
    > >"Leor Zolman" <> wrote in message
    > >news:...

    >
    > >> Are you talking about how you think the compiler is (internally)
    > >> implementing return-by-value semantics for objects, or are you

    referring
    > >to
    > >> the case where the return type is actually declared as a reference? If

    the
    > >> latter, OK; if the former, however, I say "not necessarily" (and for

    all I
    > >> know, that may even be the case for the latter). Consider this program:
    > >>

    > >
    > >I'm not really describing either a reference (or a const) as such, just
    > >trying to point out that when an object is returned, regardless of how it

    is
    > >actually implememented (e.g., in a register), you can't *change* that

    value,
    > >any more than you can change it if it's a primitive type.

    >
    > Did you take a look at that program I posted? What is the line:
    >
    > cout << ++makeSmall(10) << endl;
    >
    > doing if not "changing the value" of the object returned by makeSmall() ?


    That statement is calling the object's operator ++(), which is simply a
    function call. The value that gets changed its member variable "value", not
    the return value of makeSmall itself. And calling a function on that object
    is perfectly legal, whether or not that function changes any of the object's
    member values.

    The reason I used the term "reference" earlier is that it is the same as if
    you had declared and initialized a reference variable. You can't tell that
    variable that it now refers to something else (thus changing the variable),
    but you *can* access the object's members freely.

    As far as I know, there is not even any way (semantically) to change the
    value that's returned from a function when that value is an object (even if
    the compiler would allow it), because all operations on that object are
    implemented as operators, which are simply function calls.

    >
    > You wouldn't have been able to do that if the return value was a primitive
    > type, but you /can/ do it with a user-defined type.
    >
    > > But you *can*
    > >access its members. What I was trying to say, I guess, was that the

    object
    > >is not actually sitting in that register or unnamed location, because it

    is
    > >(at least potentially) too big. Rather, the compiler places an address
    > >there (wherever *there* is), and that address points to the actual data.
    > >This is true of return semantics as well as parameters passed by value:

    when
    > >passing an object by value, the address of the object is actually passed,
    > >not the contents of the object. That fact is what enables you to make

    use
    > >of the address to call functions or access members of the object.

    >
    > And what I was trying to say is you can't say any of that.


    ? And I thought I was in agreement with you in this thread. Are you now
    disputing that fact that you can access members and functions of a returned
    object but cannot modify primitives returned by value? Or are you simply
    saying that the way I've described it *may* be how it is implemented, but
    not how it is *required* to be implemented?

    -Howard
    Howard, May 14, 2004
    #8
  9. DaKoadMunky

    Leor Zolman Guest

    On Fri, 14 May 2004 18:31:09 GMT, "Howard" <> wrote:

    >
    >"Leor Zolman" <> wrote in message
    >news:...
    >> Did you take a look at that program I posted? What is the line:
    >>
    >> cout << ++makeSmall(10) << endl;
    >>
    >> doing if not "changing the value" of the object returned by makeSmall() ?

    >
    >That statement is calling the object's operator ++(), which is simply a
    >function call.


    Yes, it technically invokes an operator function, but the semantics still
    reflect a direct modification of an rvalue that's being returned from the
    function.

    >The value that gets changed its member variable "value", not
    >the return value of makeSmall itself. And calling a function on that object
    >is perfectly legal, whether or not that function changes any of the object's
    >member values.
    >
    >The reason I used the term "reference" earlier is that it is the same as if
    >you had declared and initialized a reference variable. You can't tell that
    >variable that it now refers to something else (thus changing the variable),
    >but you *can* access the object's members freely.


    Right; and I was focusing on the scenario when it isn't any kind of a
    reference, but actually just a temporary in wherever place the compiler
    sees fit to put it for the best performance. In a register, for example.
    Applying ++ to it, even though it is going "through" a member function
    (semantically at least, but inlined) would actually be a direct
    modification to the object, compiling into code that just increments the
    value in that alleged register.

    >
    >As far as I know, there is not even any way (semantically) to change the
    >value that's returned from a function when that value is an object (even if
    >the compiler would allow it), because all operations on that object are
    >implemented as operators, which are simply function calls.


    I don't know; I read that passage I quoted from the Standard at the start
    of this thread one way, now I detect some wiggle room in the wording (so
    what else is new?) but I have a hard time getting the image of that object
    being changed "directly" by the ++ operator out of my head...

    >
    >>
    >> You wouldn't have been able to do that if the return value was a primitive
    >> type, but you /can/ do it with a user-defined type.
    >>
    >> > But you *can*
    >> >access its members. What I was trying to say, I guess, was that the

    >object
    >> >is not actually sitting in that register or unnamed location, because it

    >is
    >> >(at least potentially) too big. Rather, the compiler places an address
    >> >there (wherever *there* is), and that address points to the actual data.
    >> >This is true of return semantics as well as parameters passed by value:

    >when
    >> >passing an object by value, the address of the object is actually passed,
    >> >not the contents of the object. That fact is what enables you to make

    >use
    >> >of the address to call functions or access members of the object.

    >>
    >> And what I was trying to say is you can't say any of that.

    >
    >? And I thought I was in agreement with you in this thread. Are you now
    >disputing that fact that you can access members and functions of a returned
    >object but cannot modify primitives returned by value?


    No, I never meant to give the impression I disputed those things. I just
    have trouble making a distinction between invoking a mutating member
    function vs. "modifying the object". It seems the two go hand-in-hand; how
    can you successfully invoke a mutating function, yet not "modify the
    object", or, in your words, " change the value that's returned from a
    function when that value is an object" ?

    >Or are you simply
    >saying that the way I've described it *may* be how it is implemented, but
    >not how it is *required* to be implemented?


    That I /was/ definitely saying. I got the feeling your argument was based
    on the actual bits of the return value being a pointer/ref of some kind,
    and I was trying to show that this need not be the scenario; in the
    scenario I'm thinking about, the actual object is returned "directly" and
    operated upon "directly". I just don't see how the C++ language mechanism
    of "applying a member function" (such as an inlined operator++()) somehow
    removes the "directness" of the operation such that you can no longer
    correctly say you're modifying the object when you apply such a function.

    I must admit I'm speaking from the gut here, and am not familiar enough
    with the Standardese that defines these mechanisms formally. In some of the
    past cases where I've gotten involved in conceptual discussions like this,
    I eventually learned the vocabulary that proved me wrong, or at least
    proved I'd been using the wrong words. I wouldn't be the least bit
    surprised if the same thing ends up happening here...
    -leor

    >
    >-Howard
    >


    --
    Leor Zolman --- BD Software --- www.bdsoft.com
    On-Site Training in C/C++, Java, Perl and Unix
    C++ users: download BD Software's free STL Error Message Decryptor at:
    www.bdsoft.com/tools/stlfilt.html
    Leor Zolman, May 14, 2004
    #9
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    7
    Views:
    600
    Victor Bazarov
    May 9, 2005
  2. Replies:
    1
    Views:
    560
    Daniel Pitts
    Nov 16, 2007
  3. Replies:
    1
    Views:
    298
    Kai-Uwe Bux
    Jun 3, 2008
  4. Daniel Pitts
    Replies:
    7
    Views:
    470
  5. Richard Maher
    Replies:
    12
    Views:
    323
    Richard Maher
    Sep 8, 2013
Loading...

Share This Page