How to return by universal reference or by value when needed?

Viewed 81

I tried to reduce my problem to a minimal example

#include <string>
#include <utility>

template <typename T> struct some_traits;

template <typename T> using traits = some_traits<std::remove_cvref_t<T>>;

struct reference_struct {
    std::string s;
};

template <> struct some_traits<reference_struct> {
    template <typename T> // T is constrained to reference_struct
    static auto &&get(T &&t) {
        return std::forward<T>(t).s;
    }
};

struct by_value_struct {
    std::string s;
};

template <> struct some_traits<by_value_struct> {
    template <typename T> // T is constrained to by_value_struct
    static auto get(T &&t) {
        return t.s;
    }
};

template <typename T> static auto &&element(T &&t) { return traits<T>::template get(std::forward<T>(t)); }

// overload that returns by value
template <typename T> static auto element(T &&t) { return traits<T>::template get(std::forward<T>(t)); }

int main() {
    reference_struct r;
    by_value_struct b;
    auto &&reference_result = element(r);
    auto by_value_result = element(b);
}

In the example, we see some_traits, which can be specialized for different structs to implement different static get methods.

Then there is a universal global auto&& element(T&&) function, where T is a struct with some_traits implemented (in the actual code T is constrained using concepts).

Some structs might implement some_traits::get by returning by reference, others might return by value.

auto get is used because the structs can return any type, not just std::string.

The above code does not compile, because calls to get are ambiguous. If the "overload that returns by value" is not present, there is of course a compiler warning stating that element returns a reference to a temporary. I tried specializing:

template <> static auto element(by_value_struct&& t) { return traits<by_value_struct>::template get(std::move(t)); }
template <> static auto element(by_value_struct& t) { return traits<by_value_struct>::template get(t); }
template <> static auto element(const by_value_struct& t) { return traits<by_value_struct>::template get(t); }

But the template specializations could not be matched due to the return type mismatch (auto vs auto&&).

My question is, can I create a function with a more general interface, which returns either by universal reference or by value (copy)? I mean in a way the outside user does not have to know beforehand which one will get returned.

0 Answers
Related