Reputation: 415
The problem I tried to tackle today is the one where you want to return a reference, but you actually can't because in some specific case you return something 'empty'. Just to clarify, something like this:
std::array<std::string, 5> cStrings /* do something to init */
const std::string& get_my_string(/* args */) {
if(i_know_what_to_return()) {
/*
* returning happily something in
* cStrings if I know what
*/
} else {
/* oeps... i need to return something, I don't have :-( */
return std::string(); // DON'T TRY THIS AT HOME!
}
}
I was wondering if there is not a generic approach to avoid the creation of empty objects all over the place, or, even worse, start returning copies of the objects. So I was wondering if I could not create some sort of template class that allows me to tell the compiler to manage this for me. This was my approach (although its only one of many, all with a small variation in the template declaration since this produces an error)
template<typename T, decltype(T) V>
struct empty_reference {
using type = T;
static const T static_object_;
operator T&() const {
return static_object_;
}
};
template<typename T, decltype(T) V>
const typename empty_reference<T, V>::type
empty_reference<T, V>::static_object_ = V;
unfortunately, this does not work (I get an error on 'decltype(T) V' saying 'decltype expects an expression not a type'), but I guess this is mainly because I am missing something in the template declaration. In the end I am hoping to use this class by returning
return empty_reference<std::string, std::string()>();
So I have three questions here;
Upvotes: 4
Views: 5226
Reputation: 415
So at some point I figured it out, at least I guess.
template<typename T>
struct empty_reference {
using type = T;
static const type static_object_;
operator const type&() {
return static_object_;
}
};
template<typename T>
const typename empty_reference<T>::type
empty_reference<T>::static_object_ = {};
The concept is that the compiler will 'generate' one static 'static_object_' member for all types you use it for. Therefore, for any type you used the empty_reference for, a static object is generated in your application and returned when they are requested (i.e. if you return empty_reference<int>
in two different files, a reference to the same int value will be returned).
The key was to remove the 'decltype(T) V
' completely and use the universal initialization (i.e. {}
) to make sure that any 'empty' constructor is called to initialize the object.
The downside is the reference needs to be constant (otherwise you can edit your 'empty reference') and the type is required to be constructable using an empty constructor.
So I guess the only question remaining is whether it is a good idea (I am not going to just my own ideas). Happy to receive any other suggestions/feedback :-)
Upvotes: -1
Reputation: 50550
Not exactly the same, but you can get your string as a function parameter instead of as a return value and rely on the fact that temporaries bind to const references.
As an example:
#include <string>
#include <iostream>
#include <utility>
struct S {
template<typename F>
void get(bool b, F &&f) {
std::forward<F>(f)(b ? s : "");
}
std::string s{"foo"};
};
int main() {
S s;
s.get(true, [](const auto &str) { std::cout << str << std::endl; });
s.get(false, [](const auto &str) { std::cout << str << std::endl; });
}
This can be a valid alternative if libraries like Boost are not already part of your project and you don't want to include them.
Otherwise, as others have mentioned, you can pick up an upcoming utility called std::optional
and combine it with std::reference_wrapper
as it follows:
#include <string>
#include <iostream>
#include <experimental/optional>
#include <functional>
struct S {
std::experimental::optional<std::reference_wrapper<std::string>> get(bool b) {
return b ? std::ref(s) : std::experimental::optional<std::reference_wrapper<std::string>>{};
}
std::string s{"foo"};
};
int main() {
S s;
auto opt1 = s.get(true);
std::cout << (opt1 ? opt1->get() : "-") << std::endl;
auto opt2 = s.get(false);
std::cout << (opt2 ? opt2->get() : "-") << std::endl;
}
Pretty ugly indeed. Note that a std::optional
should be verified through its operator bool
or the member method has_value
to be sure that it contains a value.
Unfortunately you cannot use directly a std::reference_wrapper
as return value, for it cannot be (let me say) _empty). In other terms, if you want to construct such an object, you must pass a valid reference to its constructor.
Another approach would be by using a template class like the following one:
#include <string>
#include <type_traits>
#include <iostream>
template<typename T>
struct defval {
static const std::decay_t<T> value;
};
template<typename T>
const std::decay_t<T> defval<T>::value = T{};
struct S {
const std::string & get(bool b) {
return b ? str : defval<std::string>::value;
}
std::string str{"foo"};
};
int main() {
S s;
std::cout << s.get(true) << std::endl;
std::cout << s.get(false) << std::endl;
}
Note that you must specialize it for those types that are not default constructible.
Upvotes: 2
Reputation: 170074
References must be bound to an object. So you can't return a reference in your case. Your options are:
boost::optional
, which can hold a reference (unlike the soon to be added std::optional
).Upvotes: 0