9 Replies - 8218 Views - Last Post: 14 August 2013 - 01:51 PM

#1 ishkabible  Icon User is offline

  • spelling expret
  • member icon




Reputation: 1622
  • View blog
  • Posts: 5,709
  • Joined: 03-August 09

Why reading the standard can matter

Post icon  Posted 09 August 2013 - 08:33 AM

Today I saw a small quote from the C++ standard

section 12.2 part 3 of n3242 states the following

Quote

Temporary objects are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created. This is true even if that evaluation ends in throwing an exception. The value computations and side effects of destroying a temporary object are associated only with the full-expression, not with any specific subexpression.


this says that the destructor of a temporary is to be called AFTER the WHOLE expression of a statement and NOT at the end of each subexpression. I assumed that the destructor was called at the end of each subexpression but rather it only happens after the whole thing is evaluated. This is curious because it makes some really strange expressions actually valid.

the following code is valid
std::string x = "hello, ", y = "world!";
const char* str;
str = (x + y).c_str(), std::cout << str;



however the following is not valid
std::string x = "hello, ", y = "world!";
const char* str;
str = (x + y).c_str(); 
std::cout << str;


the temporary string created by (x + y) is destroyed at the end of the expression "str = (x + y).c_str()" and as such the c string associated with it is invalidated as well.

to see this semantic difference more clearly you can put printing statements in the destructors of some class

struct foo {
  foo() {
    std::cout << "created a foo\n";
  }
  ~foo() {
    std::cout << "destroyed a foo\n";
  }
  foo make_temp() {
    return foo{};
  }
}

int main() {
  foo test;
  test.make_temp().make_temp().make_temp().make_temp();
}



you'll see that it prints out all the 'created' lines first and THEN all the 'destroyed' lines.

Now under normal usage patterns this probably doesn't effect you too much but it is one of those little things where the standard actually defines behavior that many people might not even be aware of. I always find it so fascinating that there is behavior in the language that I use everyday but I'm not even aware of or thought it worked a different way (as is this case). Even more so I am amazed at how I keep learning new things about C++; I've skimed the whole standard and even read in full some portions of it; I've used the language for almost 5 years and one of my primary hobbies is trying to do strange and unusual things with C++; all this and yet I keep finding nuggets of things I still don't know.

Have you ever come across some niche behavior that you either ended up using or were surprised to find out about? What does it say about C++'s complexity that I, and others perhaps, keep on finding things that we were previously unaware of? Is C++ too complex or is it a tribute to its design that I can use these features and not worry too much about the details (i.e. I can operate at a higher level than worrying about petty details like order of destruction)?

edit:
note that there are also 2 exceptions to the rule above

Quote

There are two contexts in which temporaries are destroyed at a different point than the end of the full-
expression. The first context is when a default constructor is called to initialize an element of an array. If
the constructor has one or more default arguments, the destruction of every temporary created in a default
argument is sequenced before the construction of the next array element, if any.

The second context is when a reference is bound to a temporary. The temporary to which the reference is
bound or the temporary that is the complete object of a subobject to which the reference is bound persists


the second one is pretty interesting however as it means the following will work
#include <iostream>
#include <string>

struct foo {
  foo() {
    std::cout << "created a foo\n";
  }
  ~foo() {
    std::cout << "destroyed a foo\n";
  }
  foo make_temp() {
    return foo{};
  }
};

int main() {
  foo test;
  foo&& rtest = test.make_temp();
}



likewise the following is actually defined behavior.
std::string a = "hello, ", b = "world!";
std::string&& str = a + b;
std::cout << str;



the temporary that was created by "a + b" persists becuase it was bound the the rvalue reference 'str' and thus its scope is the same as that of the reference it is bound to. theoretically this rule should also apply to lvalue references as well but I'm not sure how you would bind a temporary to an lvalue reference.

This post has been edited by ishkabible: 09 August 2013 - 08:56 AM


Is This A Good Question/Topic? 3
  • +

Replies To: Why reading the standard can matter

#2 ishkabible  Icon User is offline

  • spelling expret
  • member icon




Reputation: 1622
  • View blog
  • Posts: 5,709
  • Joined: 03-August 09

Re: Why reading the standard can matter

Posted 09 August 2013 - 10:24 AM

Also if you like this kind of stuff as much as me but are into C# instead I saw this video a couple of days ago called "Abusing C#". The particularly interesting bit (well to me) in that talk was about overload resolution (deciding which overload gets accepted as the one to call). This is one of my favorite topics C++ as well and something I abuse often when writing code that does tricky things at compile time. I've had to use C# for school but I was amazed none the less that C# was doing similarly tricky compile time overload selection. C# has prooven to me that it is FAR more than a Java clone. If anything I think Java has some catching up to do with C#

This post has been edited by ishkabible: 09 August 2013 - 01:31 PM

Was This Post Helpful? 0
  • +
  • -

#3 vividexstance  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 651
  • View blog
  • Posts: 2,225
  • Joined: 31-December 10

Re: Why reading the standard can matter

Posted 09 August 2013 - 11:14 AM

View Postishkabible, on 09 August 2013 - 11:33 AM, said:

likewise the following is actually defined behavior.
std::string a = "hello, ", b = "world!";
std::string&& str = a + b;
std::cout << str;



the temporary that was created by "a + b" persists becuase it was bound the the rvalue reference 'str' and thus its scope is the same as that of the reference it is bound to. theoretically this rule should also apply to lvalue references as well but I'm not sure how you would bind a temporary to an lvalue reference.

From what I gather, anything that can be referred to with a name is an lvalue reference, or as Scott Meyers puts it a "universal reference". Since it has a name, the address-of(&) operator can be called on it, and only lvalue's can have the address-of operator called on it. From my understanding, this is one of the reasons, if not the reason, why std::move needs to be called inside move constructors and move assignment operations.
Was This Post Helpful? 0
  • +
  • -

#4 Ryano121  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 1362
  • View blog
  • Posts: 3,002
  • Joined: 30-January 11

Re: Why reading the standard can matter

Posted 09 August 2013 - 01:19 PM

View Postishkabible, on 09 August 2013 - 06:24 PM, said:

C# has proven to me that it is FAR more than a Java clone. If anything I think Java has some catching up to do with C#


You're not the only one. In terms of language features C# is miles ahead of Java. In its update next year we get the addition of lambda expressions which have been a huge part of C# for years now.
Was This Post Helpful? 0
  • +
  • -

#5 ishkabible  Icon User is offline

  • spelling expret
  • member icon




Reputation: 1622
  • View blog
  • Posts: 5,709
  • Joined: 03-August 09

Re: Why reading the standard can matter

Posted 09 August 2013 - 01:22 PM

@vividexstance:

I love that talk! Unfortunately I think your a little off base on what it said. Anything with a name is an 'lvalue' (not an lvalue reference) but an 'lvalue reference' is a reference to an lvalue. In my example 'str' is itself an lvalue because it has a name BUT it is also a reference to an rvalue which makes it an 'rvalue reference'. What Myers calls a 'Universal Reference' are rvalue references to unmangled (no const, volatile, etc..) deduced types. They are Universal in the sense that they can actually boil down to either an lvalue reference or to a rvalue reference and the rules of deduction play well into this fact. I never got any reply posts on this topic about it. If you haven't seen that video WATCH. IT. NOW.

Quote

Since it has a name, the address-of(&) operator can be called on it, and only lvalue's can have the address-of operator called on it.
From my understanding, this is one of the reasons, if not the reason, why std::move needs to be called inside move constructors and move assignment operations.

this is correct (well mostly). in my example 'str' is an lvalue (that happens to reference an rvalue) if you use it, it behaves just like 'a' or 'b' would and will notably NOT invoke the move version of an overload.

the exception to the 'only lvalue's can have the address-of operator' rule is when the address of operator is overloaded. That said, DONT OVERLOAD THE ADDRESS OF OPERATOR! Every time you overload the address of operator a baby kitten, dolphin, and seal are each simultaneously clubbed to death.

This post has been edited by ishkabible: 09 August 2013 - 01:49 PM

Was This Post Helpful? 0
  • +
  • -

#6 ishkabible  Icon User is offline

  • spelling expret
  • member icon




Reputation: 1622
  • View blog
  • Posts: 5,709
  • Joined: 03-August 09

Re: Why reading the standard can matter

Posted 09 August 2013 - 01:29 PM

View PostRyano121, on 09 August 2013 - 09:19 PM, said:

View Postishkabible, on 09 August 2013 - 06:24 PM, said:

C# has proven to me that it is FAR more than a Java clone. If anything I think Java has some catching up to do with C#


You're not the only one. In terms of language features C# is miles ahead of Java. In its update next year we get the addition of lambda expressions which have been a huge part of C# for years now.


C++'s bind, C++11's lambda, and other things in <functional> along with using Haskell for about a year now (and hopefully soon in a more major project) I've really come to love higher order programming so having lambda functions in C# is a HUGE boon over Java IMO. I prefer structural typing of lambdas rather than nominal (as is the case in C# and the interface based schema of the proposal in Java) but never the less I use them a lot in C#. I've scared a few people who were newer to C# with my code :P/>

This post has been edited by ishkabible: 09 August 2013 - 01:46 PM

Was This Post Helpful? 0
  • +
  • -

#7 vividexstance  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 651
  • View blog
  • Posts: 2,225
  • Joined: 31-December 10

Re: Why reading the standard can matter

Posted 10 August 2013 - 07:47 AM

What's the difference between an lvalue and an lvalue reference?
Was This Post Helpful? 0
  • +
  • -

#8 ishkabible  Icon User is offline

  • spelling expret
  • member icon




Reputation: 1622
  • View blog
  • Posts: 5,709
  • Joined: 03-August 09

Re: Why reading the standard can matter

Posted 10 August 2013 - 08:58 AM

an lvalue is the value itself but an lvalue reference is a reference to an lvalue. no two distinct lvalues can't have the same address but two lvalue references can. this distinction is very seldom actually an issue in code (it is perhaps an issue that it ever matters frankly) but this terminology is useful in defining the behavior of references.

for the most part there is no difference in their usage but you can see some cracks in the facade in a few places

#include <iostream>

template<class T>
struct foo {
    foo() {
        std::cout << "I'm a plain type\n";
    }
};

template<class T>
struct foo<T&> {
    foo() {
        std::cout << "I'm a refrence type\n";
    }
};

int main() {
    std::string a = "hello, world!";
    std::string& ref = a;
    foo<decltype(a)> test1;
    foo<decltype(ref)> test2;
}



that is, decltype(a) and decltype(ref) do not behave the same and as such the following prints 2 different things. however the slightest modification to this yields a different result; check out the following

#include <iostream>

template<class T>
struct foo {
    foo() {
        std::cout << "I'm a plain type\n";
    }
};

template<class T>
struct foo<T&> {
    foo() {
        std::cout << "I'm a refrence type\n";
    }
};

int main() {
    std::string a = "hello, world!";
    std::string& ref = a;
    foo<decltype((a))> test1;
    foo<decltype((ref))> test2;
}



they now both yield the same thing. decltype treats identifiers by them self differently from other expressions and because of this the difference can be seen. this is perhaps an issue with decltype frankly and the difference can be a bit annoying. I've gotten in the habit of wrapping all identifiers in parens when using decltype because it is more uniform to handle. If I want just the plain type I'll use std::remove_reference<>. decltype was perhaps not defined very well and some of the big committee members have pointed it out.

edit:
In case it wasn't clear, 'a' is an lvalue and 'ref' is an lvalue reference. the lvalue reference is ALSO an lvalue as well or rather it is often treated the same. saying it "is an lvalue" get's a little murky for me personally. My point is that lvalues and lvalue references are frequently treated the same way.

This post has been edited by ishkabible: 10 August 2013 - 04:07 PM

Was This Post Helpful? 1
  • +
  • -

#9 ccubed  Icon User is offline

  • It's That Guy
  • member icon

Reputation: 160
  • View blog
  • Posts: 1,403
  • Joined: 13-June 08

Re: Why reading the standard can matter

Posted 14 August 2013 - 09:26 AM

View Postishkabible, on 09 August 2013 - 09:33 AM, said:

std::string x = "hello, ", y = "world!";
const char* str;
str = (x + y).c_str(); 
std::cout << str;



I always thought that this was common sense. Doesn't everyone know not to do this? I didn't consider it wrong for the reasons you listed granted, but it seems like this should be common sense.

Rule 1: Don't assign a reference to the output of a side effect function that isn't bound to a variable.

Edit: Actually reading the part you quoted from the standard, I would rephrase the question to: Why would anyone think a temporary result variable would persist any longer than the current line it exists on?

This post has been edited by ccubed: 14 August 2013 - 09:35 AM

Was This Post Helpful? 0
  • +
  • -

#10 ishkabible  Icon User is offline

  • spelling expret
  • member icon




Reputation: 1622
  • View blog
  • Posts: 5,709
  • Joined: 03-August 09

Re: Why reading the standard can matter

Posted 14 August 2013 - 01:51 PM

Quote

I always thought that this was common sense


The example you posted is intended be easily seen as bad by anyone with even a small amount of experience (which would thus be common sense). The other example I posted is VALID code that I would've otherwise said was INVALID had I not read that part of the standard. notice the subtle difference of 1 character. I changed a semi colon to a comma.

Quote

Why would anyone think a temporary result variable would persist any longer than the current line it exists on?


You misunderstand what I am surprised by I think. I thought that temporary expressions were only valid for the time they where being used which is a MUCH more narrow window than just to the end of a line. MORE OVER I present an exception where a temporary CAN outlive the line it was created on! That also surprised me as I would've thought there was no way in hell that would ever happen.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1