2
\$\begingroup\$

I have design a little class to allow to overload a function with universal references for a known type, but I'm not sure if that class will work as expected in any contexts.

The indended purpose is to allow overload a ''universal reference'' templated function, for specific types:

template<class arg_t>
void fun(arg_t&& obj); // obj will be just forwarded

// Overload for std::string but unaware of qualifiers, for
// forwarding purposes.
template<class arg_t = std::string¿?>
void fun(arg_t&& str);

C++ currently doesn't offer any feature to allow that (templated qualifiers). So, my class is like a std::reference_wrapper, but transporting in a second template parameter the decayed type, to write things like:

// Overloads the generic version for a std::string qualified family.
template<class qualified_t>
void fun(uref<qualified_t, std::string> uref)
{ fun2(uref.get()); } // No need to do std::forward

The get() return type will be std::string&, std::string const& or std::string&&.

That is the best I was able to achieve:

template<class ref_type_t, class type_t = std::decay_t<ref_type_t> >
class uref
{
    ref_type_t o;

public:
    using type = type_t;
    using ref_type = ref_type_t;

    static_assert(std::is_reference<ref_type>::value,
                  "ref_type must be a reference");

    explicit uref(ref_type t) : o(std::forward<ref_type>(t)) {}

    ref_type get() { return std::forward<ref_type>(o); }
    type const& get() const { return o; }

    template<class T, class = 
        std::enable_if_t<std::is_convertible_v<ref_type, T>
                    and !std::is_same_v<std::decay_t<T>, type>> >
    operator T() const { return get(); }

    template<class T, class = 
        std::enable_if_t<std::is_convertible_v<type const&, T>
                    and !std::is_same_v<std::decay_t<T>, type>> >
    operator T() { return get(); }

    operator type const&() const & { return o; }
    operator type&() & { return o; }
    operator ref_type() && { return std::forward<ref_type>(o); }
};

template<class type>
uref<type&&> make_uref(type&& t)
{ return uref<type&&>(std::forward<type>(t)); }

The way of using it is:

void client(float) { std::cout << "Floating" << std::endl; }

void client(std::string const&)
{ std::cout << "From const char* to std::string" << std::endl; }

template<class param_t>
auto service(param_t&& arg)
{ return client(make_uref(std::forward<param_t>(arg))); }

template<class ref_t>
void client(uref<ref_t, std::string>)
{ std::cout << "Specific" << std::endl; }

int main()
{
    service(3);
    service(std::string("Hi!"));
    service("Hi!");
    service(3.f);

    return 0;
}

Since uref saves the original reference type on the first template parameter T, and with get() you get the reference as-is, and you can have different "universal reference" overloads for different ''decayed'' types. With the conversion operators, the client can receive the type without begin wrapped, the conversion will free the reference bound on the service parameter. You can still use the reference wrapper to construct other objects to any convertible-to type.

The question is, is desirable to have such an object? Can be seen as an anti-pattern? For example, the two overloads, receiving a std::string and uref<T, std::string>, can confused the reader, specifically, when each one of them will be called.

Any downside of this design?

\$\endgroup\$
  • \$\begingroup\$ I'm not understanding the intent. Are you trying to re-implement std::reference_wrapper for some reason? \$\endgroup\$ – Edward May 7 '17 at 22:59
  • \$\begingroup\$ @Edward Edited to specify the purpose. \$\endgroup\$ – Peregring-lk May 8 '17 at 2:24
  • \$\begingroup\$ @Peregring-lk, are sure uref<type&&> does what you expect? \$\endgroup\$ – Incomputable May 8 '17 at 7:53
  • \$\begingroup\$ @Incomputable Do you mean inside the make_uref method? Or if the template instantiated that way behaves as I expect? \$\endgroup\$ – Peregring-lk May 8 '17 at 11:40
  • \$\begingroup\$ @Peregring-lk, inside the function. From what I remember, if one is not declaring new type parameter, && will mean rvalue reference, not universal reference. \$\endgroup\$ – Incomputable May 8 '17 at 13:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.