I have a simple one-member struct with deleted copy construction/assignment, and default move construction/assignment. I'm trying to pass one of these structs to a function by value and return the member - pretty straightforward.
struct NoCopy {
explicit NoCopy(int x) : x{x} {}
NoCopy(const NoCopy&) = delete;
NoCopy& operator=(const NoCopy&) = delete;
NoCopy(NoCopy&&) = default;
NoCopy& operator=(NoCopy&&) = default;
int x;
};
// noinline to ensure the crash is reproducible in release
// not required to reproduce the problem code
__declspec(noinline) int problem_function(NoCopy x) {
return x.x;
}
int main() {
return problem_function(NoCopy{ 1 });
}
The problem is that when compiled with MSVC, this function crashes.
Looking at the disassembly, it appears that when the copy constructor is deleted, MSVC attempts to interpret x
as if it were a NoCopy*
and the subsequent member read causes a segmentation fault.
Here's a godbolt example, with gcc and clang for reference: https://godbolt.org/z/jG7kIw
Note that both gcc and clang behave as expected. Also note that this happens in both optimised and unoptimised builds, and appears to affect both MSVC 2015 and 2017.
For reference, I'm compiling on my machine with Visual Studio Professional 2015 (14.0.25431.01 Update 3) - and I'm predominantly testing x64 builds. My platform toolset for the crash repro is set to v140.
So my question is: are there any reasonable explanations for this, or am I looking at a compiler bug.
edit: I've submitted a bug report over here
edit #2: If like me, you're stuck with a similar problem and are unable to update VS easily - it appears that defining move constructor/assignment operator manually instead of using = default
causes MSVC to spit out correct code at the call site and avoids the crash. here's a new godbolt
for this reason, things like std::unique_ptr don't seem to be affected. struct size also seems to be a factor.
= default
. – Profferproblem_function
– Stockproblem_function(NoCopy{some_value});
is legal as no copy or move actually happens.x
gets directly initialized. – Vincevincelettex was nullptr
which doesn't make sense. Note that if you change the value you pass from 0 to something else, it's clear that the implementation is trying to cast that value to aNoCopy*
. – Profferstd::cout << problem_function(NoCopy{0});
it still works fine. – Glaswegian/std:c++latest
– VincevinceletteNoCopy
is treated as an address cast toNoCopy*
. – Profferx
member is interpreted as a pointer to theNoCopy
. This example produces the same error. – Proffer