While playing with examples from Modern C++ I've written the following code.
#include <string>
#include <iostream>
static int count = 0;
class Counter
{
public:
Counter() { ++count; };
Counter(Counter& r) { ++count; };
Counter(Counter&& r) { ++count; };
~Counter() { --count; };
void foo() {};
};
decltype(auto) foo_warn()
{
Counter c;
return (c); // Warning about returning local reference
}
decltype(auto) foo_no_warn()
{
Counter c;
return 1==1 ? c : c; // No warning, still local reference returned
}
int main()
{
Counter& a = foo_warn();
Counter& b = foo_no_warn();
std::cout << count << std::endl; // prints 0
a.foo();
b.foo();
return 0;
}
Code compiled with command:
g++-6 -std=c++14 -Wall -O0 decl_fail.cpp -o decl_fail
Output:
g++-6 -std=c++14 -Wall -O0 decl_fail.cpp -o decl_fail
decl_fail.cpp: In function ‘decltype(auto) foo_warn()’:
decl_fail.cpp:19:10: warning: reference to local variable ‘a’ returned [-Wreturn-local-addr]
Counter a;
^
It's clear for me that decltype(auto)
returns the reference for expressions (but still not intuitive), therefore a
and b
are invalid references (proven by count==0
).
The question is why compiler didn't warn me about that in foo_no_warn
?
Did I just found a bug in compiler or this is some explainable behaviour?
First let's state that the problem isn't related explicitly with decltype(auto) since you would get pretty much the same result if the function returned Counter& explicitly.
You could consider the following code:
typedef std::vector<int> Type;
class DataContainer {
public:
DataContainer() : data(Type(1024, 0)) {}
const Type& getData() const { return data; }
private:
const Type data;
};
const Type& returnLocalRef()
{
DataContainer container;
const Type& data = container.getData();
return data; // o! returning a ref to local - no warning for most compilers
}
Although a local reference is returned the compiler doesn't give a warning neither in VS2015 nor gcc48 (with -Wall). However if you removed the reference from const Type& data the compiler would catch the problem right away. Should you consider such behaviour an error? Debatable.
Compier basic job is to compile the code. It warns the developer about some obvious issues but in most cases it won't do any deeper analysis of the program logic (compilation time would suffer). This is what code static analysis tools are developed and should be used for.
The described case can be considered a simple example but a single level of indirection is enough to "fool" the compiler. Since to verify this the compiler would need to check what is actually returned from the getData method.
Making a simple modification:
Type globalData;
...
const Type& getData() const { return globalData; }
would make the reference returned from the returnLocalRef function valid. Hence, this can be considered a tradeoff for the compiler between analysis complexity and time efficiency.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With