Re: anonymous array of strings // ("taking address of temporary" -
how long is temporary valid?)
On Feb 12, 11:17 am, "Alf P. Steinbach" <al...@start.no> wrote:
* Dario Saccavino:
On 11 Feb, 21:06, "Alf P. Steinbach" <al...@start.no> wrote:
Temporaries behave like being constant wrt. to binding to
references.
But they're not constant unless they have const type, and
in particular, you can call non-const non-static member
functions on a temporary of class type (unless the type is
const), which includes assignment to a temporary of class
type, because operator= is then a member function...
What is the reason for this behaviour?
I'd consider it mainly a side effect of a lot of other
decisions. Each one justified in isolation, but the final
results can be rather confusing.
It seems to me that it breaks the distinction between
R-value and L- value. The following two snippets:
X a, b, c;
a + b = c;
X f(); // extern function
void g()
{
X x;
f() += x;
}
both cause a compile error when X = int, but they become
valid (at least, with all the compilers and libraries that I
could test) if I use X = std::complex<int> or X =
std::string. And they both look like logical errors by the
programmer.
Heh heh. :-) It's much worse. Consider
struct S { int v; };
S foo() { return S(); }
int main()
{
foo() = S(); // OK, can change the whole shebang.
foo().v = 0; // Nyet! Can not change part!
}
As it happens both g++ 3.4.4 and msvc 7.1 compile this fine
(or rather, erronously), but Comeau Online 4.3.9 reports for
the last assignment that "expression must be a modifiable
lvalue".
Yep. The real problem here (or at least one way of viewing it)
is that in the first case, you're dealing with a member function
(S::operator=()---the fact that the compiler writes it, and not
you, doesn't change things here), and not an assignment
operator, so the rules concerning calling a member function
apply. In the second, of course, it's the built-in operator =
which is being used.
The built-in operator = requires an lvalue. A member function
doesn't.
Furthermore, as pointed out by some previous threads in this
newsgroup, this behaviour introduces a difference between an
operator declared as a class method and the same operator
declared as a global function;
Good observation. May relate to why operator= can only be
defined as a member function. Bjarne stated somewhere that it
was to (my words) keep some degree of sanity, something you
could rely on, and I had difficulty understanding what that
meant, but possibly it could refer to the discrimination
between rvalues and lvalues that a freestanding operator=
would give. Although I still do not quite understand it.
Possibly the rvalue/lvalue distinction doesn't really make
sense in C++.
I suspect that his statement referred more to what might happen
if the declaration of a non member operator= was visible in some
cases, and not in others. Sometimes getting the compiler
generated default when you've declared one explicitly would also
be very, very confusing.
The problem remains that the concept of lvalue/rvalue, as
inherited from C, doesn't work very well with objects of class
type. There are a couple of patterns where being able to call
member functions on a temporary object is essential. The
standard wanted to support that. Binding a temporary to a
non-const reference had proven to be seriously error prone. The
standard wanted to forbid it. (Note that these two rules
together mean that a user defined operator++ can be used on a
temporary, if it is a member function, but not if it is a free
function.) And the standard also tries to maintain the C
distinction between lvalues and rvalues where it can. The
results are confusing to say the least.
This discussion could possibly benefit from being brought to
comp.std.c++.
Except -- that group's temporarily down.
Only temporarily, I hope. (It's been down for more than a month
now.)
this difference may also lead to bugs that are hard to track
and hard to explain to novice programmers.
What do we gain from this feature?
One example that I remember being mentioned is, for a
multi-dimensional logical array, the ability to define an
operator[] that returns a proxy object that can be assigned to
(the first level proxy object would then itself also provide
an operator[], the purpose being to not expose the logical
array's internal memory layout or general implementation).
However, in most real-world cases the proxy object would not
need to be modified by the assignment, so you could define
struct Proxy
{
Proxy const& operator=( SomeElementType const& ) const;
};
That's what Scott Meyers suggests. It has the advantage that it
very much signals the object as a Proxy---the fact that
operator= is const should definitely signal something.
So I think perhaps a better example is the stringizer (or more generally
the idea of chaining calls, used e.g. in named arguments idiom),
struct Str
{
std::ostringstream stream;
template< typename T >
Str& operator<<( T const& v ) { stream << v; return *this; }
operator std::string() const { return stream.str(); }
};
void foo( std::string const& ) {}
int main()
{
foo( Str() << "Line " << 42 << "." );
}
I think that's more or less the real argument. The example I
remember was something like:
new Window( WindowParameters().background( Color::red ) ) ;
A window can typically have something like a hundred different
parameters, most of which will use the default value most of the
time. So you wrap them in a WindowParameters class, whose
constructor sets them all to the default value, and then you
call explicit functions (which return a WindowParameters&) to
change the ones you want changed.
There are possible alternative rules, but they all fall down
somewhere as well.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34