I am trying to write a function, which can either return a reference to an existing object passed as a first argument (if it is in correct state) or create and return a new object using literal passed as a second argument (default).
It would be even better if a function could take not only literal, but also another existing object as a second (default) argument and return a reference to it.
Below is a trivial implementation, but it does a lot of unneeded work:
If called with lvalue as a second (default) argument, it calls a copy constructor of an argument, that is selected for return. Ideally a reference to an object should be returned.
If called with literal as a second (default) argument, it calls constructor, copy constructor and destructor, even if second (default) argument is not selected for return. It would be better if an object is constructed and returned as rvalue reference without calling copy constructor or destructor.
std::string get_or_default(const std::string& st, const std::string& default_st) {
if (st.empty()) return default_st
else return st;
}
Is there a way to accomplish this more efficiently, while still keeping simple for caller? If I am correct, this requires a function to change return type based on run-time decision made inside a function, but I cannot think of a simple solution for caller.
I'm not 100% sure I understood the combinations of requirements but:
#include <iostream>
#include <string>
#include <type_traits>
// if called with an rvalue (xvalue) as 2:nd arg, move or copy
std::string get_or_default(const std::string& st, std::string&& default_st) {
std::cout << "got temporary\n";
if(st.empty())
return std::move(default_st); // rval, move ctor
// return std::forward<std::string>(default_st); // alternative
else
return st; // lval, copy ctor
}
// lvalue as 2:nd argument, return the reference as-is
const std::string& get_or_default(const std::string& st,
const std::string& default_st) {
std::cout << "got ref\n";
if(st.empty()) return default_st;
else return st;
}
int main() {
std::string lval = "lval";
// get ref or copy ...
decltype(auto) s1 = get_or_default("", "temporary1");
decltype(auto) s2 = get_or_default("", std::string("temporary2"));
decltype(auto) s3 = get_or_default("", lval);
std::cout << std::boolalpha;
std::cout << std::is_reference_v<decltype(s1)> << "\n";
std::cout << std::is_reference_v<decltype(s2)> << "\n";
std::cout << std::is_reference_v<decltype(s3)> << "\n";
}
Output:
got temporary
got temporary
got ref
false
false
true
Edit: Made a slightly more generic version after OP:s testing. It can use a lambda, like
auto empty_check = [](const std::string& s) { return s.empty(); };
to test if the first argument is empty.
template<typename T, typename F>
T get_or_default(const T& st, T&& default_st, F empty) {
if(empty(st)) return std::move(default_st);
// return std::forward<T>(default_st); // alternative
else return st;
}
template<typename T, typename F>
const T& get_or_default(const T& st, const T& default_st, F empty) {
if(empty(st)) return default_st;
else return st;
}