copy constructors and printf

  • Thread starter Sam Wilson [Bentley]
  • Start date
S

Sam Wilson [Bentley]

If you pass a C++ object by value as an argument to a function which has

a variable-length argument list (...), the MSVC7 C++ compiler does not
call the object's copy constructor and will not complain if the copy
constructor is private.

1) Is this part of the C++ language definition? What is the thinking
behind it?

2) In any case, how can I catch at compile time any callers that try to
pass C++ objects by value to a function like printf?
 
J

John Harrison

Sam Wilson said:
If you pass a C++ object by value as an argument to a function which has

a variable-length argument list (...), the MSVC7 C++ compiler does not
call the object's copy constructor and will not complain if the copy
constructor is private.

1) Is this part of the C++ language definition? What is the thinking
behind it?

Kind of, the C++ standard just says that such behaviour is undefined.

Its not meaningful to call a copy constructor in this case, why not call a
conversion operator instead, or a one argument non-explicit constructor of a
different class, all of these could be correct. Basically you cannot do the
right thing when you don't know what type the called function is expecting
(which may not be the same as the type supplied).
2) In any case, how can I catch at compile time any callers that try to
pass C++ objects by value to a function like printf?

You can't. If this is an issue then use a type safe IO library instead, such
as the standard iostream library.

john
 
K

Keith

John Harrison said:
Kind of, the C++ standard just says that such behaviour is undefined.

Its not meaningful to call a copy constructor in this case

Why? I don't understand that explanation. The compiler is explicitly making
a copy of the object - why would it not call the copy constructor? I can see
why it wouldn't, couldn't, and shouldn't call any other implict type
conversion to an unknown type. But making a copy is making a copy, why is
this case "special"?

If that's just the way it is, so be it, but it doesn't seem to me to be
necessary or correct that it be an "undefined" hole in the language.
, why not call a
conversion operator instead, or a one argument non-explicit constructor of a
different class, all of these could be correct. Basically you cannot do the
right thing when you don't know what type the called function is expecting
(which may not be the same as the type supplied).


You can't. If this is an issue then use a type safe IO library instead, such
as the standard iostream library.

Because it isn't just IO libraries that have varargs functions.

Keith
 
J

John Harrison

Keith said:
Why? I don't understand that explanation. The compiler is explicitly making
a copy of the object - why would it not call the copy constructor? I can see
why it wouldn't, couldn't, and shouldn't call any other implict type
conversion to an unknown type. But making a copy is making a copy, why is
this case "special"?

Well objviously it special because printf is a variadic function. You get
the same behaviour with any other variadic function.
If that's just the way it is, so be it, but it doesn't seem to me to be
necessary or correct that it be an "undefined" hole in the language.

I guess the designers of C++ made the minimum standards to be compatible
with C and not to encourage type unsafe programming. I'm not sure if what
your proposing is workable or not but I don't see any great need for it.

john
 
D

Dave Moore

John Harrison said:
Kind of, the C++ standard just says that such behaviour is undefined.

Please cite chapter and verse from the Standard when making such
assertions. In this case I think you are incorrect ... the rules for
arguments without parameters (5.2.2/7) don't say anything explicit
about the copy constructor. Basically they imply that an rvalue
argument that has class type will be used "as-is" ... which I assume
to mean that the call to the copy constructor is deferred until the
call to va_arg in the function body. AFAICS, the behavior is
well-defined unless the class is a non-POD type. For example,
consider the following example, where a format string is passed to
help the function parse the input:

#include <cstdarg>
#include "banana.h" // header file for some POD class banana

void foo (const string& format, ...) {

va_list ap;
va_start(ap, format);

// code parsing format string

if (/*some condition indicating that a banana object is expected*/)
{
banana foo = va_arg(ap, banana); // copy ctor called here
}
va_end(ap);

// etcetera
}

So, this code would generate a compiler error if the banana copy ctor
is private. Of course if the formatting string indicates a banana is
expected, but at run-time the argument is not actually a banana ...
Its not meaningful to call a copy constructor in this case, why not call a
conversion operator instead, or a one argument non-explicit constructor of a
different class, all of these could be correct. Basically you cannot do the
right thing when you don't know what type the called function is expecting
(which may not be the same as the type supplied).

This is correct, 5.2.2/7 specifies exactly what kinds of
type-conversions are applied to the arguments in the list.

You would do this in the function body, during the parsing of the
formatting string ... assuming you are defining the function ... if
not, then it depends.
You can't.

I would say this statement is too extreme .. I bet there is some
template meta-programming (TMP) wizardry that would allow you to wrap,
say, the printf function in a "delegator" type that would allow
built-in types to be passed as arguments, and emit compile-time errors
in other cases. However I am utter newbie to TMP, so I don't know
myself how to achieve it ... it just seems like the sort of problem
that is typically solvable by TMP.
If this is an issue then use a type safe IO library instead, such
as the standard iostream library.

This is indeed good advice if all the OP wants to do is use printf
.... if he is rolling his own functions taking variable argument lists,
I would advise him to make sure he really needs them. In my
experience, they usually represent a "hack" to solve a problem that is
better solved by inproving the design. In my own code, proper use of
overloading and exceptions has eliminated all the places where I once
used vararg functions.

HTH, Dave Moore
 
M

Michiel Salters

Keith said:
Why? I don't understand that explanation. The compiler is explicitly making
a copy of the object - why would it not call the copy constructor? I can see
why it wouldn't, couldn't, and shouldn't call any other implict type
conversion to an unknown type. But making a copy is making a copy, why is
this case "special"?

Because you don't make a copy. Normally, you initialize parameters with
aguments. E.g. ,

void foo( int parameter );
short int argument = 3;
foo( argument );

The 'parameter' object is initialized with the argument. Here a short to
int conversion is needed. With varargs, the destination type is not known.
Therefore the compiler has a very short and very fixed list of conversions.

User defined types are not on this list; they just invoke UB. That makes
sense, when you pass a std::string, are you initializing a std::string&
or a std::string? The former can be initialized without a copy, the
latter cannot. In C++, this can be determined only by the paramter, hence
it doesn't work with varargs.

Regards,
Michiel Salters
 
R

Rob Williscroft

Dave Moore wrote in in comp.lang.c++:
Please cite chapter and verse from the Standard when making such
assertions. In this case I think you are incorrect ... the rules for
arguments without parameters (5.2.2/7) don't say anything explicit
about the copy constructor. Basically they imply that an rvalue
argument that has class type will be used "as-is" ... which I assume

This makes no sense at all, how can an rvalue (or anything else for that
matter) be passed without copying ?
to mean that the call to the copy constructor is deferred until the
call to va_arg in the function body. AFAICS, the behavior is
well-defined unless the class is a non-POD type.

The presence of copy-ctor is enough to make a UDT non-POD,
here's how the GCC team currently interpret POD-ishness:

#include <iostream>
#include <ostream>

struct foo
{
int b;
foo( foo const & ) {}
};

extern foo create();

void f( ... )
{
}

int main()
{
f( create() );
}

test.cpp: In function `int main()':
test.cpp:19: warning: cannot pass objects of non-POD type
`struct foo' through `...'; call will abort at runtime


Rob.
 
J

Jerry Coffin

Please cite chapter and verse from the Standard when making such
assertions. In this case I think you are incorrect ... the rules for
arguments without parameters (5.2.2/7) don't say anything explicit
about the copy constructor.

Not directly, no. Indirectly, yes. If the class/struct has a non-POD
type, the behavior is undefined. To be a POD type, it must be an
aggregate (9/4). To be an aggregate, it must contain no user-declared
constructors (8.5.1/1).

According to 12.8/4, if the class does not declare a copy ctor, one is
declared implicitly. According to 12.8/7, if the copy ctor is
implicitly declared, and the code ever does anything that would use
the copy ctor, then the copy ctor is defined implicitly as well.

Thus, you can pass a POD object, and the implicitly defined copy ctor
will be used to initialize the parameter. That copy-ctor, however,
simply does member-wise copying so you can never tell if it was
invoked (therefore, the as-if rule allows it to be elided without
affecting conformance).

If, OTOH, you want to see whether it has been invoked, you must define
it. As soon as you do so, however, the class/struct is no longer an
aggregate and therefore no longer a POD. Passing it as a variadic
parameter then gives undefined behavior.

You might notice what looks like a corner case there: there are two
separate pieces that say the copy ctor is implicitly declared (12.8/4)
and defined (12.8/7) and you aren't careful, you could read in the
possibility of using an implicit declaration but an explicit
definition of the copy ctor. This is not the case though -- 12.8/7
says the copy ctor IS implicitly defined if it's implicitly declared
and it is ever used.

As such, if the copy ctor is ever used your explicit definition and
the compiler's implicit definition would violate the one-definition
rule.

As far as I can see, that leaves one particularly pointless case: you
can explicitly define the implicitly declared copy ctor if and only if
it is never used. Something to remember if you ever want to write the
world's silliest compiler conformance test suite!
So, this code would generate a compiler error if the banana copy ctor
is private.

If you don't explicitly declare the ctor, it will have an implicitly
declared ctor, which will be public (12.8/5). For the ctor to be
private, it must, therefore, be user-declared. If it's user-declared,
the struct is no longer an aggregate and therefore no longer a POD.
Since that gives undefined behavior, the compiler can legitimately do
whatever it pleases.

Obviously only sheer laziness prevents every C++ programmer from
knowing this. Start by memorizing pages 66 through 203 verbatim and it
follows naturally. :)
 
J

John Harrison

Dave Moore said:
"John Harrison" <[email protected]> wrote in message

Please cite chapter and verse from the Standard when making such
assertions. In this case I think you are incorrect ... the rules for
arguments without parameters (5.2.2/7) don't say anything explicit
about the copy constructor. Basically they imply that an rvalue
argument that has class type will be used "as-is" ... which I assume
to mean that the call to the copy constructor is deferred until the
call to va_arg in the function body. AFAICS, the behavior is
well-defined unless the class is a non-POD type.

I was assuming the OP was talking about a non-POD type. I wrongly assumed
that a user defined copy ctor meant a non-POD type, but it seems this isn't
the case.

[snip]
[snip]
You can't.

I would say this statement is too extreme .. I bet there is some
template meta-programming (TMP) wizardry that would allow you to wrap,
say, the printf function in a "delegator" type that would allow
built-in types to be passed as arguments, and emit compile-time errors
in other cases. However I am utter newbie to TMP, so I don't know
myself how to achieve it ... it just seems like the sort of problem
that is typically solvable by TMP.

I was assuming the OP didn't want to program significant extra code to solve
this problem, he wanted some quick solution.

john
 
D

Dave Moore

John Harrison said:
I was assuming the OP was talking about a non-POD type. I wrongly assumed
that a user defined copy ctor meant a non-POD type, but it seems this isn't
the case.

No, your assumption was right, I just didn't dig deep enough when
refreshing my memory as to what a POD-type is ... my bad 8*).
2) In any case, how can I catch at compile time any callers that try to
pass C++ objects by value to a function like printf?
[snip]
You can't.

I would say this statement is too extreme .. I bet there is some
template meta-programming (TMP) wizardry that would allow you to wrap,
say, the printf function in a "delegator" type that would allow
built-in types to be passed as arguments, and emit compile-time errors
in other cases. However I am utter newbie to TMP, so I don't know
myself how to achieve it ... it just seems like the sort of problem
that is typically solvable by TMP.

I was assuming the OP didn't want to program significant extra code to solve
this problem, he wanted some quick solution.
Indeed that was a logical assumption, I guess I was just feeling
pedantic .. I'll try not to let it happen again. <grin>

Dave Moore
 
D

Dave Moore

Rob Williscroft said:
Dave Moore wrote in in comp.lang.c++:


This makes no sense at all, how can an rvalue (or anything else for that
matter) be passed without copying ?

Well, I guess I see your point, but then how is a pass-by-value
argument of POD-type handled in a variadic function call? In Jerry
Coffin's response to my post in this thread, he said that a ctor
should be called "to initialize the parameter", but as I understand it
from 5.2.2/7 (which IMO is kind of vague here), there *is* no
parameter in a variadic function call (for arguments matching the ...
anyway), only an argument. So is my statement below about deferring
the copy constructor until the va_arg call correct?

OTOH, maybe there is some effect of the statement " .. lvalue to
rvalue conversion ... will be performed" in 5.2.2/7 that I am not
seeing? I would say that a POD-type object passed by value is an
rvalue, but I remember seeing somewhere in the Standard (I can't find
it anymore) that objects of class type are always passed to functions
as lvalues.

Please help me out of my confusion.

TIA, Dave Moore

P.S. I expect there is more info on this in the C-Standard, but I
don't have a copy of that.
 
R

Rob Williscroft

Dave Moore wrote in in comp.lang.c++:
Well, I guess I see your point, but then how is a pass-by-value
argument of POD-type handled in a variadic function call? In Jerry
Coffin's response to my post in this thread, he said that a ctor
should be called "to initialize the parameter", but as I understand it
from 5.2.2/7 (which IMO is kind of vague here), there *is* no
parameter in a variadic function call (for arguments matching the ...
anyway), only an argument.

The paramiter is the argument that the function recieves i.e. the paramiter
is that which the argument initializes and that which va_arg() recieves.
So is my statement below about deferring
the copy constructor until the va_arg call correct?

No. The POD class has an implicitly generated copy constructor (memberwise
copy) that is used to intialize something implementation defined, could
be stack, registers, a combination of both or maybe something else, that
will be accessable by the va_* macros in the called function.

Because in general we don't know how the va_* macro's work, show's one
reason why a class with user defined copy constuctor can't be a POD.

OTOH, maybe there is some effect of the statement " .. lvalue to
rvalue conversion ... will be performed" in 5.2.2/7 that I am not
seeing?

An example of the lvalue to rvalue conversion here is:

int &f()
{
static int i = 10;
return i;
}

int main()
{
std::printf( "%d", f() );
}

f() returns an lvalue, the conversion to rvalue ( int & -> int )
is done by making a copy. Its done by initializing the paramiter.

If the lvalue to rvalue conversion wern't done then the compiler
would be required to pass the reference, *not* what std::printf
expects.
I would say that a POD-type object passed by value is an
rvalue, but I remember seeing somewhere in the Standard (I can't find
it anymore) that objects of class type are always passed to functions
as lvalues.

You've miss remembered, the only why an lvalue can be passed to
a function is if the function takes a reference paramiter.

void f( int &param );

'param' is an lvalue and *importantly* its the lvalue that
was passed.

void f( int const &cparam );

'cparam' is an lvalue, but strangly it might be "bound" to
an rvalue, go figure :).
Please help me out of my confusion.

Hopt it helped.
P.S. I expect there is more info on this in the C-Standard, but I
don't have a copy of that.

I've no doubt it would be educational. But IIUC the C++ standard
say's everything it needs to about function calls and doesn't defer
to the C standard.

Rob.
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,167
Messages
2,570,913
Members
47,455
Latest member
Delilah Code

Latest Threads

Top