Yeah, I realized that I was returning a local temporary which belongs to foo
's call stack, and it would be destroyed after the call. And binding a reference to local temporary is just awful. But if I try to use foo
to initialize an object pair, i.e. auto p = foo();
or std::pair<const char*, int> p = foo();
, it still gets a segmentation fault. It just kind of doesn't make sense to me: const auto&
could be considered as a rvalue which is non-modifiable and can be used to assign a lvalue. When executing std::pair<const char*, int> p = foo();
, foo()
should initialize the object pair p
and then could it kill himself. Yet, the fact indicates the opposite. So, on any cases, shouldn't we return a local temporary as a const reference
, even using it as a rvalue
to assign or initialize an object?
I found some possible usage:
#include <iostream>
#include <utility>
#include <memory>
auto mypair = std::pair<const char*, int>("hi there", 2020); // gvalue
const auto& foo() {
//return std::make_pair("hi there", 2020); // bang! dead!
//auto ptr = new std::pair<const char*, int>("hi there", 2020);
//return *ptr; // Okay, return a heap allocated value, but memory leaks. Ouch!
// really wish this is a java program ;)
return mypair; // Okay, return a global value
}
auto bar() {
return std::make_unique<std::pair<const char*, int>>("hi there", 2020);
}
int main()
{
//std::pair<const char*, int> p = foo(); // copy-init
const auto& p = foo();
std::cout << "The value of pair p is:\n"
<< "(" << p.first << ", " << p.second << ")\n";
auto p2 = bar();
std::cout << "The value of pair p2 is:\n"
<< "(" << p2->first << ", " << p2->second << ")\n";
}
If returning (const) reference is required and meanwhile we must create it within the method, how could we do this?
Btw, this can really happen:

Consider a ternary search tree (TST) with key specialized as string, we just store the mapped_type
values, and use the paths the represent the keys (strings):
enum class Link : char { LEFT, MID, RIGHT };
struct Node {
char ch = '\0'; // put ch and pos together so that they will take
Link pos = Link::MID;// only 4 bytes (padding with another 2 bytes)
T* pval = nullptr; // here we use a pointer instead of an object entity given that internal
// nodes doesn't need to store objects (and thus saving memory)
Node* parent = nullptr;
Node* left{}, * mid{}, * right{};
Node() {}
Node(char c) : ch(c) {}
Node(char c, Link pos, Node* parent) : ch(c), pos(pos), parent(parent) {}
~Node() { delete pval; }
};
And, say, if we want to overload those iterator operators (the complete code can be found here):
class Tst_const_iter
{
using _self = Tst_const_iter;
public:
using iterator_category = std::bidirectional_iterator_tag;
using value_type = std::pair<const std::string, T>;
using difference_type = ptrdiff_t;
using pointer = const value_type*; // g++ needs these aliases though we don't use them :(
//using reference = const value_type&; // g++ uses `reference` to determine the type of operator*()
using reference = value_type; // but we can fool it :)
Tst_const_iter() : _ptr(nullptr), _ptree(nullptr) {}
Tst_const_iter(node_ptr ptr, const TST* ptree) : _ptr(ptr), _ptree(ptree) {}
Tst_const_iter(const Tst_iter& other) : _ptr(other.ptr()), _ptree(other.cont()) {}
// we cannot return a const reference since it's a temporary
// so return type cannot be reference (const value_type&)
value_type operator*() const {
_assert(_ptr != nullptr, "cannot dereference end() iterator");
return std::make_pair(get_key(_ptr), *(_ptr->pval));
}
// pointer operator->() const = delete;
_self& operator++() {
_assert(_ptr != nullptr, "cannot increment end() iterator");
_ptr = tree_next(_ptr);
return *this;
}
_self operator++(int) {
_assert(_ptr != nullptr, "cannot increment end() iterator");
_self tmp{ *this };
_ptr = tree_next(_ptr);
return tmp;
}
// --begin() returns end() (its pointer becomes nullptr)
_self& operator--() {
if (_ptr == nullptr) _ptr = rightmost(_ptree->root);
else _ptr = tree_prev(_ptr);
return *this;
}
// begin()-- returns a copy of begin(), then itself becomes end()
_self operator--(int) {
_self tmp{ *this };
--*this;
return tmp;
}
friend bool operator==(const _self& lhs, const _self& rhs) {
_assert(lhs._ptree == rhs._ptree, "iterators incompatible");
return lhs._ptr == rhs._ptr;
}
friend bool operator!=(const _self& lhs, const _self& rhs) {
_assert(lhs._ptree == rhs._ptree, "iterators incompatible");
return lhs._ptr != rhs._ptr;
}
// auxiliary functions
node_ptr ptr() const noexcept { return _ptr; }
const TST* cont() const noexcept { return _ptree; } // get container
std::string key() const {
_assert(_ptr != nullptr, "cannot get the key of end() iterator");
return get_key(_ptr);
}
const T& val() const {
_assert(_ptr != nullptr, "cannot get the value of end() iterator");
return *(_ptr->pval);
}
private:
node_ptr _ptr;
const TST* _ptree;
};
operator*()
is a hardass, as according to the standards, the return type should be reference
which in this case is desirable to be const std::pair<const std::string, T>&
. When we do *it
we want to get a normal pair
, just as what we do in std::map<const std::string, T>
. The problem is we don't actually have the std::string
data member, but we do have the mapped type data member which can be obtained via dereferencing the pointer, i.e. *(x->pval)
, where x
is of type node_ptr
or Node*
.