Passing a class object with trivial copy constructor, but no output?
Asked Answered
S

1

8

I tried this program with GCC and Clang, but both output nothing

#include <iostream>

struct A {
  A(){}

  template<typename T>
  A(T &) {
    std::cout << "copied!";
  }
};

void f(...) { }

int main() {
  A a;
  f(a);
}

According to my Standards reading, this program should output "copied!". Can anyone tell me whether I am mistaken or whether this is a bug in those two compilers?

Sadism answered 4/11, 2012 at 3:20 Comment(25)
That's not a "trivial copy constructor."Heteroclite
VC++ says the Ellipsis parameter results in binary copy, rather than invocation of the copy constructor. msdn.microsoft.com/en-us/library/z11y6be4(v=vs.110).aspxGandzha
@Gandzha that's more of an answer than a comment.Criseyde
Is the ... intended to be the varargs syntax or simply a placeholder for "stuff"?Heteroclite
@RayToal not the compiler specified in the question - hence a bad answer.Gandzha
@Gandzha that's incorrect! see gcc.gnu.org/bugzilla/show_bug.cgi?id=49102Sadism
@NicolBolas correctly diagnosed. it's not even a copy costructor.Sadism
Just a general comment: Downvoting a question from Johannes Schaub - litb after less than 10 minutes study is not a very smart thing to do.Bevatron
I'm probably saying something retarded - mostly a Java programmer here - but don't you need something like A a = new A(); ? Otherwise a would be null, not an instance...Fowl
@Fowl nope, this is C++.Gressorial
C++ just creates objects using the default constructor? ewwwwww. /me goes back to JavalandFowl
"According to my Standards reading, this program should output "copied!"." I looked through the standard, but I couldn't find the place where it even talks about the behavior of varargs with objects (or anything else for that matter). Where is that?Heteroclite
@Fowl the difference is you can control whether the object is in dynamic storage (A* a = new A) or automatic (A a). C++ is more powerful in this regard, and you're having an uneducated reaction to it.Gressorial
@NicolBolas the behaviour of varargs is described in the C standard, which obviously doesn't have objects.Gressorial
@SethCarnegie: Right. Since C doesn't have objects, that would make me suspect that the behavior in terms of C++ objects is undefined. Thus, this is perfectly legitimate behavior. He disagrees; I'm simply asking why he disagrees.Heteroclite
@NicolBolas it is at 5.2.2p7 in the C++ Standard.Sadism
Data, not answers: With both Apple's clang version 4.1 and with tip-of-trunk clang, I get "copied!".Bevatron
@HowardHinnant ahh thanks. Weird, I guess my Clang is too old. I'm using version 3.2Sadism
This question doesn't get a +1 from me as it doesn't explain why you think the behaviour should differ. "The standard says so" without actual quotes is not useful.Carmella
@Lightness: 5.2.2/7: [...] The lvalue-to-rvalue (4.1), [...] standard conversions are performed on the argument expression., 4.1/2: [...] Otherwise, if the glvalue has a class type, the conversion copy-initializes a temporary of type T from the glvalue [...].Braasch
If the function is inlined is there now a requirement to actually do the copy? Especially since the function does nothing. The compiler is very aggressive at doing copy elision.Sealed
Wait, I thought it was "common" knowledge that template constructors never win-out against copy-constructors. Where does that fit in here?Rhiamon
@Xeo: Yes, but that should be in the question.Carmella
@GMan: It doesn't, since it's wrong knowledge. See the comment thread on Nicol's answer.Braasch
@Loki there is nothing to elide here. I am not copying from a temporary object.Sadism
H
3

It would seem that what you expect is the behavior defined by the standard.

Template functions do not prevent the creation of copy constructors/assignment operators. So template functions don't prevent a class from being considered "trivially copyable". However, they do participate in overload resolution when it comes time to actually copy them, so they can interfere. And since a in this example is a non-const l-value, it better fits the signature A(A&) than it does A(const A&). So it calls the template function.

(Though why you didn't bother to explain all of this in your question eludes me, since you obviously did your research.)

However, considering how small of a corner-case this is, I wouldn't go around relying on this behavior to force trivially copyable classes into not being trivially copied.

Heteroclite answered 4/11, 2012 at 4:0 Comment(24)
This is wrong. The templated constructor not being a copy constructor doesn't hinder it from being called when passed a non-const lvalue of the same class type.Braasch
@Xeo: No but if there's also a synthesised [non-template] copy constructor with otherwise the same signature, then that'll be given precedence in overload resolution. This template "copy constructor" will never even be instantiated!Carmella
@Lightness: But that's not the case here, since we have a non-const lvalue argument and a T& parameter.Braasch
@JohannesSchaub-litb: Oh, I see. You're trying to cheat. You're trying to do an end-run around C++11's triviality rules, by pretending that your class is trivial, and using this template function to somehow hijack the copying to call actual code during the copy.Heteroclite
@JerryCoffin "such a" means TheClass::TheClass(TheClass)Sadism
@Xeo: I don't see the distinction. The non-template still wins in overload resolution.Carmella
I'm not a language expert, but the way I read [class.copy]/p9 is that in this example the compiler generated copy constructor of A has the signature A(const A&). And is therefore an inferior match to the templated A(T&) constructor in this example (due to the rules in [over.match] which I'm currently too lazy to narrow down to a more specific section).Bevatron
@Lightness: No it does not. The default copy ctor in this case takes an A const& parameter and we have a non-const lvalue argument, as such, the T& constructor, instantiated to A&, wins.Braasch
@HowardHinnant: [class.copy]/9 is talking about implicit generation of a move constructor, not copy constructor. The copy ctor is covered in paragraphs 7 and 8.Kevon
@Howard: Honestly, this sounds more like a bug in the spec. If a copying operation requires the class to be trivially copyable, that ought to mean that the copying operation must use the trivial copy constructor/assignment. There'd be no point in the trivial requirement if you could just back-door it and force what should be a memcpy into not being a memcpy.Heteroclite
@NicolBolas before asking, I checked open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#535 and there they didn't change 5.2.2 to say "must use a trivial copy constructor for doing the copy in the lvalue to rvalue conversion (if any)". Of course, it could still be that it was an oversight..Sadism
@Nicol: The spec specifically only says "performs lvalue-to-rvalue standard conversion", and that conversion says T(the_glvalue) is done for class types.Braasch
@NicolBolas: My bad, I quoted the paragraph number of N3242 instead of the C++11 N3290. My apologies for the confusion. Nevertheless, I believe the only thing incorrect in my comment was the paragraph number. It should have been 8 instead of 9.Bevatron
@HowardHinnant indeed, I think you've nailed it down correctly.Sadism
@Xeo: I know what the spec says, but the whole point of the triviality rules is to permit the implementation to just memcpy these things. Forcing it to call an actual copy construct is antithetical to that purpose, and therefore a bug. Remember: before C++11, it was required to be a full-fledged POD. This seems to be a bug that crept in during the changeover from POD language to trivial language.Heteroclite
@NicolBolas Perhaps the reason of the rule was for va_arg in the callee to behave sanely. I.e va_arg can rely on the fact that it can do a "as-if bitcopy". But I see no reason for the caller side not to call non-trivial code. But if so, they could have put the rule on va_arg and not on the caller...Sadism
@Johannes: This doesn't just affect varargs; it affects everything that copies the class which would exhibit undefined behavior for a non-trivially copyable type.Heteroclite
@Johannes: Or more to the point, trivially-copyable classes should have the exact same behavior whether you call the copy directly or memcpy into some properly aligned_storage memory. That's the whole point of the distinction.Heteroclite
If two experts such as yourselves can not agree on what the standard says, this looks to me like strong indication that there is a standards issue that needs clarification. Please help us clarify this issue by contacting the author of open-std.org/jtc1/sc22/wg21/docs/cwg_active.html .Bevatron
@NicolBolas "If a class is trivially copyable, then the following should have the exact same behavior" (referring to groups.google.com/a/isocpp.org/forum/?fromgroups=#!topic/…) is not how "trivially copyable" is intended to be used: It is intended to mean the compiler may do this and that optimization (as far as I collected from the guru talks). For example a private in-class defaulted copy constructor or in-class deleted copy constructor is trivial. But if the program uses it, it is ill-formed.Sadism
Ah wait, you are talking about "trivially copyable" and not about "trivial copy constructor". My god, it becomes confusing :(Sadism
@Howard: At this point, we agree that the standard says that this should work. What we disagree on now is whether this behavior is a bug in the standard. I don't know how many of the standards people read the new Google Groups on the isocpp site, but I posted a message there about this issue (though GoogleGroups's inability to do basic formatting defeated my attempts to make it readable twice now).Heteroclite
I have no idea whether it's a bug in the spec or not.Sadism
@Xeo: That rather depends on what ... is doing, which is ill-specified so far as I can tell.Carmella

© 2022 - 2024 — McMap. All rights reserved.