C++11 move to local const reference: scope [duplicate] - c++

This question already has answers here:
const reference to a temporary object becomes broken after function scope (life time)
(2 answers)
Closed 4 years ago.
For regular local const reference variables, the scope is prolonged. Which is why the following code works as expected:
#include <iostream>
#include <memory>
struct foo
{
foo()
{
std::cout << "foo() #" << (void*)this << std::endl;
}
~foo()
{
std::cout << "~foo() #" << (void*)this << std::endl;
}
};
int main()
{
auto const& f = std::make_shared<foo>();
std::cout << "f = " << f.get() << std::endl;
return 0;
}
// prints:
// foo() #0x55f249c58e80
// f = 0x55f249c58e80
// ~foo() #0x55f249c58e80
It seems though that this does not work as expected when assigning a moved object using std::move():
#include <iostream>
#include <memory>
#include <list>
struct foo
{
foo()
{
std::cout << "foo() #" << (void*)this << std::endl;
}
~foo()
{
std::cout << "~foo() #" << (void*)this << std::endl;
}
};
int main()
{
std::list<std::shared_ptr<foo>> l;
l.push_back(std::make_shared<foo>());
auto const& f = std::move(l.front());
l.clear();
std::cout << "f = " << f.get() << std::endl;
return 0;
}
// prints
// foo() #0x564edb58fe80
// ~foo() #0x564edb58fe80
// f = 0x564edb58fe80
Does std::move() indeed change the scope, or am I dealing with a compiler bug?
Changing the variable from auto const& f to just auto f fixes the problem. If I wrap the move into another function, it also works:
auto const& f = [&]() { return std::move(l.front()); }();
It's almost like std::move() does not share the same semantics as a function call, but rather as if it was just a regular variable assignment:
auto const& f = std::move(l.front());

Let's put aside std::move() and create this function:
struct sometype {};
const sometype &foobar( const sometype &cref ) { return cref; }
and now we use it:
const sometype &ref = foobar( sometype() );
What do you think, will lifetime of temporary be prolongated in this case? No it would not. Lifetime is only prolongated when you assign to a reference directly. When it goes through a function or through static_cast or std::move that prolongation is gone. So you have exactly the same issue with std::move()
struct sometype {
sometype() { std::cout << "ctype()" << std::endl; }
~sometype() { std::cout << "~ctype()" << std::endl; }
};
const sometype &foobar( const sometype &cref ) { return cref; }
int main()
{
const sometype &cref1 = foobar( sometype() );
sometype &&cref2 = std::move( sometype() );
std::cout << "main continues" << std::endl;
}
output:
ctype()
~ctype()
ctype()
~ctype()
main continues
live example
Note: you should not use std::move() on return statement, it does not give you anything.
For your code change. You should remember that std::move() does not move anything. It is done by special assignment operator or constructor (if they provided for a type). So when you write this code:
const type &ref = std::move( something );
there is no constructor nor assignment operator involved and so no moving happens. For actual moving to happen you have to assign it to a variable:
type val = std::move( something );
now moving would happen if possible or copy otherwise.

Related

C++ static function in class, return reference

After researching a bit I don't understand the output (source code below):
42
42
45
I'm fine with the second, but why I get this output?
It's coming from fooling around with avoiding global constants and variables in a bigger project. Could someone please explain it to me?
#include <iostream>
class Const
{
public:
Const() = delete;
static auto foo(int val = 42) -> int&;
};
auto Const::foo(int val) -> int&
{
static int sval = val;
return sval;
}
int main()
{
std::cout << Const::foo() << std::endl;
Const::foo(24);
std::cout << Const::foo() << std::endl;
Const::foo() = 45;
std::cout << Const::foo() << std::endl;
return 0;
}
In your code:
#include <iostream>
class Const // what a strange name for something that is not const.
{
public:
Const() = delete;
static auto foo(int val = 42) -> int&;
};
auto Const::foo(int val) -> int& // if you don't return a const reference,
// it may be modified by the caller
{
static int sval = val; // val is not const, and only initialized
// once.
return sval; // you return a reference to a mutable value.
}
int main()
{
std::cout << Const::foo() << std::endl;
Const::foo(24); // this changes nothing, the static variable sval
// has already been initialized.
std::cout << Const::foo() << std::endl;
Const::foo() = 45; // the reference returned by foo() is
// not const. SO that works.
std::cout << Const::foo() << std::endl;
return 0;
}
To fix, Const::foo() should return a const int&.
Forget about using static function variables. When entering the function, the code must check each time if its static variables have been initialized. This usually involves using a hardware fence or some other thread-safe mechanism. These will unnecessarily slow down execution, especially when accessing these const values from multiple threads.

Does this const reference have its life preserved?

I have found this answer to the question "Does a const reference prolong the life of a temporary?", which states:
Only local const references prolong the lifespan.
I'm afraid my standardese is not up to scratch to know whether foo, below, is a local const reference or not.
Does my const std::string& foo below prolong the lifetime of the temporary std::string function argument created in the call to get_or, or do I have a dangling reference?
#include <iostream>
#include <boost/optional.hpp>
struct Foo
{
const std::string& get_or(const std::string& def)
{
return str ? str.get() : def;
}
boost::optional<std::string> str;
};
int main()
{
Foo f;
const std::string& foo = f.get_or("hello world");
std::cout << foo << '\n';
}
const& won't extend lifetimes in that situation. Consider the example here that constructs a temporary and then attempts to print it: it's using the same constructs as your code, but I've altered it to make object construction and destruction more explicit to the user.
#include <iostream>
struct reporting {
reporting() { std::cout << "Constructed" << std::endl;}
~reporting() { std::cout << "Destructed" << std::endl;}
reporting(reporting const&) { std::cout << "Copy-Constructed" << std::endl;}
reporting(reporting &&) { std::cout << "Move-Constructed" << std::endl;}
reporting & operator=(reporting const&) { std::cout << "Copy-Assigned" << std::endl; return *this;}
reporting & operator=(reporting &&) { std::cout << "Move-Assigned" << std::endl; return *this;}
void print() const {std::cout << "Printing." << std::endl;}
};
const reporting& get_or(const reporting& def)
{
return def;
}
int main()
{
const reporting& foo = get_or(reporting{});
foo.print();
return 0;
}
Output:
Constructed
Destructed
printing.
Note how the object is destroyed before printing. is displayed.
You might be wondering why the code still completes with no visible errors: it's the result of Undefined Behavior. The object in question doesn't exist, but because it doesn't depend on state to invoke its method, the program happens to not crash. Other, more complicated examples should carry no guarantee that this will work without crashing or causing other, unexpected behavior.
Incidentally, things are a little different if the temporary is bound directly to the const&:
#include <iostream>
struct reporting {
reporting() { std::cout << "Constructed" << std::endl;}
~reporting() { std::cout << "Destructed" << std::endl;}
reporting(reporting const&) { std::cout << "Copy-Constructed" << std::endl;}
reporting(reporting &&) { std::cout << "Move-Constructed" << std::endl;}
reporting & operator=(reporting const&) { std::cout << "Copy-Assigned" << std::endl; return *this;}
reporting & operator=(reporting &&) { std::cout << "Move-Assigned" << std::endl; return *this;}
void print() const {std::cout << "printing." << std::endl;}
};
const reporting& get_or(const reporting& def)
{
return def;
}
int main()
{
const reporting& foo = reporting{};
foo.print();
return 0;
}
Output:
Constructed
printing.
Destructed
See how the object isn't destroyed until after it is used. In this situation, the object survives until the end of scope.
You passed the string through too many references.
Binding the temporary string to the def parameter of get_or extends the lifetime of the string to the end of the full expression containing the function call, but binding def to the return value of get_or and binding the return value of get_or to foo do not extend the lifetime further. The string is dead by the time you try to print it.
The "temporary" in question is the std::string-object created when calling get_or with a parameter of type const char*. The lifetime of this temporary object is limited with the end of function get_or, and the fact that you return a reference to this temporary and assign it afterwards does not prolong the lifetime. See the following code which uses a simple "custom" string class, which couts construction and destruction:
class MyString {
public:
MyString (const char* str) {
m_str = strdup(str);
cout << "constructor MyString - '" << m_str << "'" << endl;
}
~MyString() {
cout << "destructor MyString - '" << m_str << "'" << endl;
free(m_str);
}
char *m_str;
};
struct Foo
{
const MyString& get_or(const MyString& def)
{
cout << "Foo::get_or with '" << def.m_str << "'" << endl;
return def;
}
};
int main()
{
Foo f;
const MyString& foo = f.get_or("hello world");
cout << "usage of foo?" << endl;
}
Output:
constructor MyString - 'hello world'
Foo::get_or with 'hello world'
destructor MyString - 'hello world'
usage of foo?
Note that the destructor is called before you will have the chance to use foo.
The situation is different if you assign a reference to a temporary directly. Again, the lifetime is until the end of the function main, but it will be used in main and not in any function calling main:
const MyString& foo2 = MyString("hello world2");
cout << "usage of foo..." << endl;
Then the output will be:
constructor MyString - 'hello world2'
usage of foo...
destructor MyString - 'hello world2'

Perfect forwarding, why does the destructor get called twice?

I'm trying to make a function that mimics Python's with statement but I've run into some interesting behavior that I don't quite understand.
With the following program:
#include <iostream>
struct foo {
foo() { std::cout << "foo()" << std::endl; }
~foo() { std::cout << "~foo()" << std::endl; }
};
auto make_foo() -> foo {
return {};
}
template <typename T, typename F>
auto with(T&& t, F&& fn) -> void {
fn(std::forward<T>(t));
}
auto main() -> int {
std::cout << "before" << std::endl;
with(make_foo(), [](auto f) {
std::cout << "during" << std::endl;
});
std::cout << "after" << std::endl;
}
When compiled under with the clang provided by Xcode 6.3 and -std=c++14 and run I get the following output:
before
foo()
during
~foo()
~foo()
after
Does anybody know why I am getting two ~foo()'s in my output?
Here are the two objects:
with(make_foo(), [](auto f) {
1^^^^^^^^^ 2^^^^^^
There is the object returned by make_foo(), and the function argument f.
If you pass by reference (change to auto&& f) then you will only see evidence of one object.
There's no creation message because this is created by copy/move construction and you do not have any output in those constructors.
Note that there may be more objects inside make_foo() but your compiler is doing copy elision.
Your destructor calls don't appear to be matched with constructor calls simply because you aren't tracing copy/move constructors. If we add the tracing like so:
struct foo {
foo() { std::cout << "foo()" << std::endl; }
~foo() { std::cout << "~foo()" << std::endl; }
foo(const foo&) { std::cout << "foo(const foo&)" << std::endl; }
foo(foo&&) { std::cout << "foo(foo&&)" << std::endl; }
};
our output is now:
before
foo()
foo(foo&&)
during
~foo()
~foo()
after
The reason for the move-construction is that your lambda takes its parameter by value:
[](auto f) {
// ^^^^^^
std::cout << "during" << std::endl;
}
If you don't want the copy, take by reference-to-const, or maybe even forwarding reference.
This works for me by accepting an r-reference in the lambda function parameter to prevent a copy being made:
#include <iostream>
struct foo {
foo() { std::cout << "foo()" << std::endl; }
~foo() { std::cout << "~foo()" << std::endl; }
};
auto make_foo() -> foo {
return {};
}
template <typename T, typename F>
auto with(T&& t, F&& fn) -> void {
fn(std::forward<T>(t));
}
auto main() -> int {
std::cout << "before" << std::endl;
with(make_foo(), [](auto&&) { // r-reference!
std::cout << "during" << std::endl;
});
std::cout << "after" << std::endl;
}
New Improved Output:
before
foo()
during
~foo()
after

In which situations is the C++ copy constructor called?

I know of the following situations in c++ where the copy constructor would be invoked:
when an existing object is assigned an object of it own class
MyClass A,B;
A = new MyClass();
B=A; //copy constructor called
if a functions receives as argument, passed by value, an object of a class
void foo(MyClass a);
foo(a); //copy constructor invoked
when a function returns (by value) an object of the class
MyClass foo ()
{
MyClass temp;
....
return temp; //copy constructor called
}
Please feel free to correct any mistakes I've made; but I am more curious if there are any other situations in which the copy constructor is called.
When an existing object is assigned an object of it own class
B = A;
Not necessarily. This kind of assignment is called copy-assignment, meaning the assignment operator of the class will be called to perform memberwise assignment of all the data members. The actual function is MyClass& operator=(MyClass const&)
The copy-constructor is not invoked here. This is because the assignment operator takes a reference to its object, and therefore no copy-construction is performed.
Copy-assignment is different from copy-initialization because copy-initialization is only done when an object is being initialized. For example:
T y = x;
x = y;
The first expression initializes y by copying x. It invokes the copy-constructor MyClass(MyClass const&).
And as mentioned, x = y is a call to the assignment operator.
(There is also something called copy-elison whereby the compiler will elide calls to the copy-constructor. Your compiler more than likely uses this).
If a functions receives as argument, passed by value, an object of a class
void foo(MyClass a);
foo(a);
This is correct. However, note that in C++11 if a is an xvalue and if MyClass has the appropriate constructor MyClass(MyClass&&), a can be moved into the parameter.
(The copy-constructor and the move-constructor are two of the default compiler-generated member functions of a class. If you do not supply them yourself, the compiler will generously do so for you under specific circumstances).
When a function returns (by value) an object of the class
MyClass foo ()
{
MyClass temp;
....
return temp; // copy constructor called
}
Through return-value optimization, as mentioned in some of the answers, the compiler can remove the call to the copy-constructor. By using the compiler option -fno-elide-constructors, you can disable copy-elison and see that the copy-constructor would indeed be called in these situations.
I might be wrong about this, but this class allows you to see what is called and when:
class a {
public:
a() {
printf("constructor called\n");
};
a(const a& other) {
printf("copy constructor called\n");
};
a& operator=(const a& other) {
printf("copy assignment operator called\n");
return *this;
};
};
So then this code:
a b; //constructor
a c; //constructor
b = c; //copy assignment
c = a(b); //copy constructor, then copy assignment
produces this as the result:
constructor called
constructor called
copy assignment operator called
copy constructor called
copy assignment operator called
Another interesting thing, say you have the following code:
a* b = new a(); //constructor called
a* c; //nothing is called
c = b; //still nothing is called
c = new a(*b); //copy constructor is called
This occurs because when you when you assign a pointer, that does nothing to the actual object.
Situation (1) is incorrect and does not compile the way you've written it. It should be:
MyClass A, B;
A = MyClass(); /* Redefinition of `A`; perfectly legal though superfluous: I've
dropped the `new` to defeat compiler error.*/
B = A; // Assignment operator called (`B` is already constructed)
MyClass C = B; // Copy constructor called.
You are correct in case (2).
But in case (3), the copy constructor may not be called: if the compiler can detect no side effects then it can implement return value optimisation to optimise out the unnecessary deep copy. C++11 formalises this with rvalue references.
This is basically correct (other than your typo in #1).
One additional specific scenario to watch out for is when you have elements in a container, the elements may be copied at various times (for example, in a vector, when the vector grows or some elements are removed). This is actually just an example of #1, but it can be easy to forget about it.
There are 3 situations in which the copy constructor is called:
When we make copy of an object.
When we pass an object as an argument by value to a method.
When we return an object from a method by value.
these are the only situations....i think...
The following are the cases when copy constructor is called.
When instantiating one object and initializing it with values from another object.
When passing an object by value.
When an object is returned from a function by value.
Others have provided good answers, with explanations and references.
In addition, I have written a class to check the different type of instantations/assigments (C++11 ready), within an extensive test:
#include <iostream>
#include <utility>
#include <functional>
template<typename T , bool MESSAGES = true>
class instantation_profiler
{
private:
static std::size_t _alive , _instanced , _destroyed ,
_ctor , _copy_ctor , _move_ctor ,
_copy_assign , _move_assign;
public:
instantation_profiler()
{
_alive++;
_instanced++;
_ctor++;
if( MESSAGES ) std::cout << ">> construction" << std::endl;
}
instantation_profiler( const instantation_profiler& )
{
_alive++;
_instanced++;
_copy_ctor++;
if( MESSAGES ) std::cout << ">> copy construction" << std::endl;
}
instantation_profiler( instantation_profiler&& )
{
_alive++;
_instanced++;
_move_ctor++;
if( MESSAGES ) std::cout << ">> move construction" << std::endl;
}
instantation_profiler& operator=( const instantation_profiler& )
{
_copy_assign++;
if( MESSAGES ) std::cout << ">> copy assigment" << std::endl;
}
instantation_profiler& operator=( instantation_profiler&& )
{
_move_assign++;
if( MESSAGES ) std::cout << ">> move assigment" << std::endl;
}
~instantation_profiler()
{
_alive--;
_destroyed++;
if( MESSAGES ) std::cout << ">> destruction" << std::endl;
}
static std::size_t alive_instances()
{
return _alive;
}
static std::size_t instantations()
{
return _instanced;
}
static std::size_t destructions()
{
return _destroyed;
}
static std::size_t normal_constructions()
{
return _ctor;
}
static std::size_t move_constructions()
{
return _move_ctor;
}
static std::size_t copy_constructions()
{
return _copy_ctor;
}
static std::size_t move_assigments()
{
return _move_assign;
}
static std::size_t copy_assigments()
{
return _copy_assign;
}
static void print_info( std::ostream& out = std::cout )
{
out << "# Normal constructor calls: " << normal_constructions() << std::endl
<< "# Copy constructor calls: " << copy_constructions() << std::endl
<< "# Move constructor calls: " << move_constructions() << std::endl
<< "# Copy assigment calls: " << copy_assigments() << std::endl
<< "# Move assigment calls: " << move_assigments() << std::endl
<< "# Destructor calls: " << destructions() << std::endl
<< "# " << std::endl
<< "# Total instantations: " << instantations() << std::endl
<< "# Total destructions: " << destructions() << std::endl
<< "# Current alive instances: " << alive_instances() << std::endl;
}
};
template<typename T , bool MESSAGES>
std::size_t instantation_profiler<T,MESSAGES>::_alive = 0;
template<typename T , bool MESSAGES>
std::size_t instantation_profiler<T,MESSAGES>::_instanced = 0;
template<typename T , bool MESSAGES>
std::size_t instantation_profiler<T,MESSAGES>::_destroyed = 0;
template<typename T , bool MESSAGES>
std::size_t instantation_profiler<T,MESSAGES>::_ctor = 0;
template<typename T , bool MESSAGES>
std::size_t instantation_profiler<T,MESSAGES>::_copy_ctor = 0;
template<typename T , bool MESSAGES>
std::size_t instantation_profiler<T,MESSAGES>::_move_ctor = 0;
template<typename T , bool MESSAGES>
std::size_t instantation_profiler<T,MESSAGES>::_copy_assign = 0;
template<typename T , bool MESSAGES>
std::size_t instantation_profiler<T,MESSAGES>::_move_assign = 0;
Here is the test:
struct foo : public instantation_profiler<foo>
{
int value;
};
//Me suena bastante que Boost tiene una biblioteca con una parida de este estilo...
struct scoped_call
{
private:
std::function<void()> function;
public:
scoped_call( const std::function<void()>& f ) : function( f ) {}
~scoped_call()
{
function();
}
};
foo f()
{
scoped_call chapuza( [](){ std::cout << "Exiting f()..." << std::endl; } );
std::cout << "I'm in f(), which returns a foo by value!" << std::endl;
return foo();
}
void g1( foo )
{
scoped_call chapuza( [](){ std::cout << "Exiting g1()..." << std::endl; } );
std::cout << "I'm in g1(), which gets a foo by value!" << std::endl;
}
void g2( const foo& )
{
scoped_call chapuza( [](){ std::cout << "Exiting g2()..." << std::endl; } );
std::cout << "I'm in g2(), which gets a foo by const lvalue reference!" << std::endl;
}
void g3( foo&& )
{
scoped_call chapuza( [](){ std::cout << "Exiting g3()..." << std::endl; } );
std::cout << "I'm in g3(), which gets an rvalue foo reference!" << std::endl;
}
template<typename T>
void h( T&& afoo )
{
scoped_call chapuza( [](){ std::cout << "Exiting h()..." << std::endl; } );
std::cout << "I'm in h(), which sends a foo to g() through perfect forwarding!" << std::endl;
g1( std::forward<T>( afoo ) );
}
int main()
{
std::cout << std::endl << "Just before a declaration ( foo a; )" << std::endl; foo a;
std::cout << std::endl << "Just before b declaration ( foo b; )" << std::endl; foo b;
std::cout << std::endl << "Just before c declaration ( foo c; )" << std::endl; foo c;
std::cout << std::endl << "Just before d declaration ( foo d( f() ); )" << std::endl; foo d( f() );
std::cout << std::endl << "Just before a to b assigment ( b = a )" << std::endl; b = a;
std::cout << std::endl << "Just before ctor call to b assigment ( b = foo() )" << std::endl; b = foo();
std::cout << std::endl << "Just before f() call to b assigment ( b = f() )" << std::endl; b = f();
std::cout << std::endl << "Just before g1( foo ) call with lvalue arg ( g1( a ) )" << std::endl; g1( a );
std::cout << std::endl << "Just before g1( foo ) call with rvalue arg ( g1( f() ) )" << std::endl; g1( f() );
std::cout << std::endl << "Just before g1( foo ) call with lvalue ==> rvalue arg ( g1( std::move( a ) ) )" << std::endl; g1( std::move( a ) );
std::cout << std::endl << "Just before g2( const foo& ) call with lvalue arg ( g2( b ) )" << std::endl; g2( b );
std::cout << std::endl << "Just before g2( const foo& ) call with rvalue arg ( g2( f() ) )" << std::endl; g2( f() );
std::cout << std::endl << "Just before g2( const foo& ) call with lvalue ==> rvalue arg ( g2( std::move( b ) ) )" << std::endl; g2( std::move( b ) );
//std::cout << std::endl << "Just before g3( foo&& ) call with lvalue arg ( g3( c ) )" << std::endl; g3( c );
std::cout << std::endl << "Just before g3( foo&& ) call with rvalue arg ( g3( f() ) )" << std::endl; g3( f() );
std::cout << std::endl << "Just before g3( foo&& ) call with lvalue ==> rvalue arg ( g3( std::move( c ) ) )" << std::endl; g3( std::move( c ) );
std::cout << std::endl << "Just before h() call with lvalue arg ( h( d ) )" << std::endl; h( d );
std::cout << std::endl << "Just before h() call with rvalue arg ( h( f() ) )" << std::endl; h( f() );
std::cout << std::endl << "Just before h() call with lvalue ==> rvalue arg ( h( std::move( d ) ) )" << std::endl; h( std::move( d ) );
foo::print_info( std::cout );
}
This is an abstract of the test compiled with GCC 4.8.2 with -O3 and -fno-elide-constructors flags:
Normal constructor calls: 10
Copy constructor calls: 2
Move constructor calls: 11
Copy assigment calls: 1
Move assigment calls: 2
Destructor calls: 19
Total instantations: 23
Total destructions: 19
Current alive instances: 4
Finally the same test with copy elision enabled:
Normal constructor calls: 10
Copy constructor calls: 2
Move constructor calls: 3
Copy assigment calls: 1
Move assigment calls: 2
Destructor calls: 11
Total instantations: 15
Total destructions: 11
Current alive instances: 4
Here is the complete code running at ideone.

const value and RVO

Say I have this function:
template <class A>
inline A f()
{
A const r(/* a very complex and expensive construction */);
return r;
}
Is it a good idea to declare r const, since a const variable cannot be moved? Note that the returned value is not const. The qualm I am grappling is, that r truly is const, but it may not be a good idea to declare it as such. Yet the qualifier should be helping the compiler generate better code.
As demonstrated here, NRVO elides the copy of r implied by the line return r;
#include <iostream>
struct A {
const char* name;
A( const char* name_ ):name(name_) { std::cout << "created " << name << "\n"; }
A(A const&){ std::cout << "copied " << name << "\n"; }
A(A &&){ std::cout << "moved " << name << "\n"; }
};
A f() {
std::cout << "start of f()\n";
A const r("bob");
std::cout << "body of f()\n";
return r;
}
int main() {
A x = f();
}
And the copy in main is also elided.
If you block NRVO and RVO in some other way (for instance using the flag -fno-elide-constructors when compiling with GCC), the const can cause your object to be copied instead of moved. You can see this if we remove the copy constructor from A:
#include <iostream>
struct A {
const char* name;
A( const char* name_ ):name(name_) { std::cout << "created " << name << "\n"; }
//A(A const&){ std::cout << "copied " << name << "\n"; }
A(A &&){ std::cout << "moved " << name << "\n"; }
};
A f() {
std::cout << "start of f()\n";
A const r("bob");
std::cout << "body of f()\n";
return r;
}
int main() {
A x = f();
}
the code no longer compiles. While the copy constructor isn't executed so long as NRVO occurs, its existence is required by your const local variable.
Now, NRVO requires a few things, such as a single variable which is returned along every single execution path of the function in question: if you ever "abort" and do a return A(), NRVO is blocked, and your const local variable suddenly forces a copy at all return sites.
If class A is under your control, and you want to return const objects by move, you can do
mutable bool resources_were_stolen = false;
and set that to true in a const move constructor
A(const A&& other) { ...; other.resources_were_stolen = true; }
~A() { if (!resources_were_stolen) ... }
Actually, the destructor probably would become if (resources_were_stolen) some_unique_ptr.release();, using the fact that objects lose their const-ness during construction and destruction.