Yes, the behavior is compliant.
Per paragraph 6.5.4/1 of the C++11 Standard:
For a range-based for
statement of the form
for ( for-range-declaration : expression ) statement
let range-init be equivalent to the expression surrounded by parentheses
( expression )
and for a range-based for
statement of the form
for ( for-range-declaration : braced-init-list ) statement
let range-init be equivalent to the braced-init-list. In each case, a range-based for statement is equivalent to
{
auto && __range = range-init;
for ( auto __begin = begin-expr,
__end = end-expr;
__begin != __end;
++__begin ) {
for-range-declaration = *__begin;
statement
}
}
In your case, the returned shared pointer is dereferenced, and the object it points to is bound to the __range
reference. However, the shared pointer itself is not copied, nor is it bound to a reference that would prolong its lifetime. Therefore, it goes out of scope. Being the last shared pointer referencing the pointed object, the object gets destroyed too.
Things would have been different if you had returned your Test
object by value, rather than returning a shared pointer:
Test func() {
return Test();
}
int main() {
for (const auto &obj: func()) {
std::cout << obj << '\n';
}
}
This way, the Test
temporary returned by func()
is bound to the __range
reference, and its lifetime is prolonged to match the lifetime of the reference.
Here is a live example.