Topic: Temporaries: Const or mutable?
Author: "Samee Zahur" <samee.zahur@gmail.com>
Date: Fri, 13 May 2005 00:04:34 CST Raw View
This is a followup to two recent threads in c.l.c++
-
http://groups-beta.google.com/group/comp.lang.c++/browse_thread/thread/532943e5798f58db/e3aa4aac4225190b?q=Mutating+temporaries+author:Samee&rnum=1#e3aa4aac4225190b
-
http://groups-beta.google.com/group/comp.lang.c++/browse_thread/thread/3ae26ddfaf040d62/556029a16ad33bcd?q=Mutating+temporaries+author:Samee&rnum=2#556029a16ad33bcd
It seems that C++ treats temporary objects rather strangely
(to me at least):
- temps of built-in types and enums (or their typedefs) are always
const ... after all, statements such as 1+2=val; wouldn't make sense!
- temps of all other types are mutable if used directly - the
following is legal: myclass().mutatewith(54);
or even myclass()=54;if operator= is defined in myclass.
- the same temps when passed to a function by reference, however,
must be passed as a 'const myclass&', which means if temps are to be
mutated from within the function, a const_cast is needed (or is that
undefined?)
Why does such a discrepency exist? I mean, the whole idea of C++ is to
have user-defined types that act like 'first-class citizens', right?
Even though user-defined temporaries might often have to be mutated -
why can't something like that be done inside a function receiving it by
reference? Restrictions like these often seems to be getting in the way
of const-correctness as far as I see :(
Samee
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: "illumanus@yahoo.com" <illumanus@yahoo.com>
Date: Fri, 13 May 2005 21:44:50 CST Raw View
The reason temporaries are not allowed to be passed to functions
accepting arguments by reference has to do with implicit conversions.
Look at the following code:
struct MyString {
....
MyString (char *); // Defines an implicit conversion from char * to
MyString
MyString &toupper(); // Converts our string to uppercase and returns
*this
};
// Some "handy" functions
void toupper(MyString &s) { s.toupper(); };
bool isupper(const MyString &); // Returns true if our string is all
uppercase
MyString make_str() { return "Foo"; };
..
toupper("Foo"); // This will generate an error and should
// It would cause a temporary MyString to be constructed, modified then
destroyed
// Not what the coder probably intends from writing this statement.
if(isupper("Foo")) // This is ok
cout << "Foo";
if(isupper(make_str().toupper()); // This is ok too
cout << "Upper Foo"
This rule is to protect coders. Without this rule you could accidently
generate code that might appear to do something but really does nothing
and does not generate a compile time error.
Hope this helps,
Lance Gatlin
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: "Me" <anti_spam_email2003@yahoo.com>
Date: 16 May 2005 05:30:12 GMT Raw View
It doesn't really come from the constness of builtins. It comes from
the way C treats l-values and the fact that it didn't have const early
on and there was already a lot of code written. Your code touches on a
specific issue so lets take a digression:
T foo()
{
return T();
}
foo() = T();
works for structs/classes, doesn't work for primitives. Note that if T
was a struct/class, you can't do:
foo().i = 50;
The way to protect against this is:
const T cfoo()
{
return T();
}
cfoo() = T();
Now it doesn't work as expected. But note: for primitive types the
top-level const is redundant, so it generates the same function
signature. The problem with your code is that it was constructing a T
using:
T() // similar to foo()
you cannot do:
const T() // would be similar to cfoo()
because there is a space between it (except as an extension in
Microsoft's compiler which allows spaces in certain cases). You can get
around this with either a typedef:
typedef const T cT;
cT()
or with a template helper involving a typedef:
template<class T> struct ident { typedef T type; };
ident<const T>::type()
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: llewelly.at@xmission.dot.com (Llewelly)
Date: Mon, 16 May 2005 16:21:23 GMT Raw View
"Samee Zahur" <samee.zahur@gmail.com> writes:
> This is a followup to two recent threads in c.l.c++
> -
> http://groups-beta.google.com/group/comp.lang.c++/browse_thread/thread/532943e5798f58db/e3aa4aac4225190b?q=Mutating+temporaries+author:Samee&rnum=1#e3aa4aac4225190b
http://xrl.us/f3z2
(A shortening of Samee's link in case someone's browser breaks that
looong thing.)
> -
> http://groups-beta.google.com/group/comp.lang.c++/browse_thread/thread/3ae26ddfaf040d62/556029a16ad33bcd?q=Mutating+temporaries+author:Samee&rnum=2#556029a16ad33bcd
http://xrl.us/f3z2
(Shortened.)
>
>
> It seems that C++ treats temporary objects rather strangely
> (to me at least):
>
> - temps of built-in types and enums (or their typedefs) are always
> const ... after all, statements such as 1+2=val; wouldn't make sense!
>
> - temps of all other types are mutable if used directly - the
> following is legal: myclass().mutatewith(54);
> or even myclass()=54;if operator= is defined in myclass.
Ha, saw that yesterday in 'real code'. We has something like this:
void strip_non_hex_digits(std::vector<char>& c)
{
for
( std::vector<char>::iterator current= c.begin()
; current != c.end()
; ++current
)
{
if( !std::isxdigit(*current) )
{
current = --c.erase(current); //Using -- on a temporary.
}
}
}
gcc compiles this happily. But aCC does not. The problem? gcc defines
operator-- for iterators as a member function, which can be used
on temporaries. If it had been defined as a non-member operator--,
this would not compile. aCC doesn't compile because aCC uses
something like 'typedef T* iterator' for its vectors. (All 3
choices are allowed by the standard)
The lesson? Because of the rule you cite, defining an operator as
member is almost always a bad thing, unless the language requires
it.
There's a lot of debate about whether this rule is good or bad, but
I'm firmly convinced it was a mistake. It makes member functions
inconsistent with the rest of the language in yet another way, it
makes treatment of temporaries inconsistent, and it makes it
member operators inconsistent with built-in operators. However it
can't be fixed without breaking lots of code.
>
> - the same temps when passed to a function by reference, however,
> must be passed as a 'const myclass&', which means if temps are to be
> mutated from within the function, a const_cast is needed (or is that
> undefined?)
>
> Why does such a discrepency exist?
Plenty of member functions are important not becuase of their effects
on the values within an object, but becuase of their effects on
some state external to the object.
I don't recall specific code examples, or the many other arguments
that went into it, but you can find endless threads on this topic
in the archives. Watch out, however - in pre-standard days there
was a time when the drafts allowed temporaries to be bound to
references to non-const, and plenty of popular compilers
implemented things that way.
> I mean, the whole idea of C++ is to
> have user-defined types that act like 'first-class citizens', right?
> Even though user-defined temporaries might often have to be mutated -
> why can't something like that be done inside a function receiving it by
> reference? Restrictions like these often seems to be getting in the way
> of const-correctness as far as I see :(
[snip]
You aren't the only that thinks it's a mistake.
I seem to recall there are number of historically informative papers
at:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/
(Look before 1998. )
But I don't recall specificly which ones.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: musiphil@bawi.org (Seungbeom Kim)
Date: Mon, 16 May 2005 16:22:18 GMT Raw View
illumanus@yahoo.com wrote:
> The reason temporaries are not allowed to be passed to functions
> accepting arguments by reference has to do with implicit conversions.
But there is more than that, as you see below:
> struct MyString {
> ....
> MyString (char *); // Defines an implicit conversion from char * to
> MyString
> MyString &toupper(); // Converts our string to uppercase and returns
> *this
> };
>
> // Some "handy" functions
> void toupper(MyString &s) { s.toupper(); };
> bool isupper(const MyString &); // Returns true if our string is all
> uppercase
> MyString make_str() { return "Foo"; };
toupper(MyString("Foo")) would not compile even if it involves no
implicit conversions.
I have been annoyed by this gratuitous restriction several times. When
the programmer explicitly wants a temporary, it should be allowed.
bool test(game&);
void test_games()
{
if (not test(game(args...)
or not test(game(different args...)
or not test(game(still different args...))
{
// we have a problem
}
}
OR
void extract_int(std::istream& is);
void extract_int(std::string& s) { extract_int(std::istringstream(s)); }
Imagine how these codes would be cluttered with meaningless declarations
even though the temporaries don't really have to be named and declared.
I feel it's silly to have to write an rvalue-to-lvalue converter such as
X& self() { return *this; } to overcome this restriction.
A bunch of references: :-)
http://groups-beta.google.com/group/comp.lang.c++.moderated/msg/ac40d36f0cc8039b
http://groups-beta.google.com/group/comp.lang.c++.moderated/msg/9cc442a4cb661f65
http://groups-beta.google.com/group/comp.lang.c++.moderated/msg/96d566cb68601705
http://groups-beta.google.com/group/comp.lang.c++.moderated/msg/67d0dcc09bdebcab
http://groups-beta.google.com/group/comp.lang.c++.moderated/msg/2dc43f0d4dc0cf85
http://groups-beta.google.com/group/comp.lang.c++.moderated/msg/d35bedd6980e719f
> This rule is to protect coders. Without this rule you could accidently
> generate code that might appear to do something but really does nothing
> and does not generate a compile time error.
The protection is sometimes excessive. As you noted, it should be on the
implicit conversion, but it is actually on a bigger category of things
("temporaries" or "rvalues") now.
Isn't it ever going to be changed, really?
--
Seungbeom Kim
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: "Samee Zahur" <samee.zahur@gmail.com>
Date: Mon, 16 May 2005 11:22:56 CST Raw View
> toupper("Foo"); // This will generate an error and should
Ok, I understand that this wouldn't compile, but I still don't quite
get the possible confusion. Even if I forget about the rest of the code
(and the MyString class) for a while, I'd think I am seeing a literal
string getting converted to uppercase and then getting thrown away ...
under what confusion or purpose might a programmer write something like
this, from which he has to be protected?
Samee
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: musiphil@bawi.org (Seungbeom Kim)
Date: Mon, 16 May 2005 22:04:29 GMT Raw View
Llewelly wrote:
>
> There's a lot of debate about whether this rule is good or bad, but
> I'm firmly convinced it was a mistake. It makes member functions
> inconsistent with the rest of the language in yet another way, it
> makes treatment of temporaries inconsistent, and it makes it
> member operators inconsistent with built-in operators. However it
> can't be fixed without breaking lots of code.
Can you elaborate on the possibility of breaking existing codes?
Can't we avoid it if we change the rules only in the "loosening"
direction, so that some of the currently ill-formed codes turn into
well-formed ones but not the other way round?
--
Seungbeom Kim
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]