I am seeing a difference in generated code depending on whether I explicitly default the copy constructor, or hand-write the same thing. It's a simple class that only holds an int and defines some arithmetic operators on it.
Both clang and g++ handle this situation in similar ways, so it made me wonder if there is an underlying language requirement for this, and if so, what's it doing? Looking for citations in the standard if possible. :)
To show this in action, I wrote the average() function two ways, operating on raw ints and also on Holders. I expected the two to generate the same code. Here is the output:
Explicitly defaulted copy constructor:
average(Holder, Holder):
add esi, edi
mov eax, esi
shr eax, 31
add eax, esi
sar eax
ret
average(int, int):
add esi, edi
mov eax, esi
shr eax, 31
add eax, esi
sar eax
ret
It is the same! Awesome, right? The question arises when I forget to "default" the implementation, and simply hand-write the version. Up until now I was under the impression that this should have the same resulting code as the defaulted code, but it doesn't.
hand written copy constructor
average(Holder, Holder):
mov edx, DWORD PTR [rdx]
mov ecx, DWORD PTR [rsi]
mov rax, rdi
add ecx, edx
mov edx, ecx
shr edx, 31
add edx, ecx
sar edx
mov DWORD PTR [rdi], edx
ret
average(int, int):
add esi, edi
mov eax, esi
shr eax, 31
add eax, esi
sar eax
ret
I'm trying to understand the reason for this, and relevant citations from the standard are most appreciated.
Here is the code
#define EXPLICITLY_DEFAULTED_COPY_CTOR true
class Holder {
public:
#if EXPLICITLY_DEFAULTED_COPY_CTOR
Holder(Holder const & other) = default;
#else
Holder(Holder const & other) noexcept : value{other.value} { }
#endif
constexpr explicit Holder(int value) noexcept : value{value} {}
Holder& operator+=(Holder rhs) { value += rhs.value; return *this; }
Holder& operator/=(Holder rhs) { value /= rhs.value; return *this; }
friend Holder operator+(Holder lhs, Holder rhs) { return lhs += rhs; }
friend Holder operator/(Holder lhs, Holder rhs) { return lhs /= rhs; }
private:
int value;
};
Holder average(Holder lhs, Holder rhs) {
return (lhs + rhs) / Holder{2};
}
int average(int lhs, int rhs) {
return (lhs + rhs) / int{2};
}
If this is expected, then is there anything I can do to the hand-written implementation that will get it to generate the same code as the defaulted version? I thought noexcept might help, but it doesn't.
Notes: If I add move constructor, the same issue remains except this difference happens with it instead of the copy constructor. It's the underlying reason I'm seeking, not just workarounds. I'm not interested in a code review or comments on style that are not directly relevant to answering why the code generation is different, because this is heavily minimized to show the issue I'm asking about.
See it live on Godbolt: https://godbolt.org/g/YA5Zsq
static_assert(std::is_trivially_copyable<Holder>::value);
to one of the functions and the header<type_traits>
. It's no longer trivially copyable See Trivial copy constructor in: en.cppreference.com/w/cpp/language/copy_constructor Suggest you add the [language-lawyer] tag – Richard Critten{}
to initialize the value rather than()
? It might make a difference. – Mark Ransom