Disclaimer: Goal of research is how to disable copy elision and return value optimization for supplied part of code. Please avoid from answering if want to mention something like XY-problem. The question has strictly technical and research character and is formulated strongly in this way
In C++14 there was introduced copy elision and return value optimization. If some object had been destructed and copy-constructed in one expression, like copy-assignment or return immediate value from function by value, copy-constructor is elided.
Following reasoning is applied to copy constructor, but similar reasoning can be performed for move constructor, so this is not considered further.
There are some partial solutions for disabling copy elision for custom code:
1) Compiler-dependent option. For GCC, there is solution based on __attribule__
or #pragma GCC
constructions, like this https://mcmap.net/q/1166154/-disable-return-value-optimization-for-one-function . But since it compiler-dependent, it does not met question.
2) Force-disabling copy-constructor, like Clazz(const Clazz&) = delete
. Or declare copy-constructor as explicit
to prevent it's using. Such solution does not met task since it changes copy-semantics and forces introducing custom-name functions like Class::copy(const Clazz&)
.
3) Using intermediate type, like describe here https://mcmap.net/q/1166155/-how-can-i-disable-c-return-value-optimization-for-one-type-only . Since this solution forces to introduce new descendant type, it does not met question.
After some research there was found that reviving temporary value can solve question. If reinterpret source class as reference to one-element array with this class and extract first element, then copy elision will turned off. Template function can be written like this:
template<typename T, typename ... Args> T noelide(Args ... args) {
return (((T(&)[1])(T(args...)))[0]);
}
Such solution works good in most cases. In following code it generates three copy-constructor invocations - one for direct copy-assignment and two for assignment with return from function. It works good in MSVC 2017
#include <iostream>
class Clazz {
public: int q;
Clazz(int q) : q(q) { std::cout << "Default constructor " << q << std::endl; }
Clazz(const Clazz& cl) : q(cl.q) { std::cout << "Copy constructor " << q << std::endl; }
~Clazz() { std::cout << "Destructor " << q << std::endl; }
};
template<typename T, typename ... Args> T noelide(Args ... args) {
return (((T(&)[1])(T(args...)))[0]);
}
Clazz func(int q) {
return noelide<Clazz>(q);
}
int main() {
Clazz a = noelide<Clazz>(10);
Clazz b = func(20);
const Clazz& c = func(30);
return 0;
}
This approach works good for a
and b
cases, but performs redundant copy with case c
- instead of copy, reference to temporary should be returned with lifetime expansion.
Question: how to modify noelide
template to allow it work fine with const lvalue-reference with lifetime expansion?
Thanks!
func
does return value, which is bound to const referencec
. But copy-constructor is invoked, which is erroneous. – Playreaderfunc
, butnoelide
. clang says "error: C-style cast from rvalue to reference type 'Clazz (&)[1]'", and g++ something similar. VC++ is notoriously unreliable. – Kristynkrocka
andb
variables, and should not be invoked toc
variable. Thanks in advance – Playreader#pragma GCC optimize ("no-elide-constructors")
applied, but cross-compiler. – Playreader