Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to write a value wrapper class

Tags:

c++

c++11

Supose I need to write a class which acts as a wrapper for values:

template<typename T>
struct value_wrapper
{
    T value;

    value_wrapper( const T& v ) : value( v ) {}

    //etc...
};

The class is dessigned to be used as an alias of the original value, so if that value was an rvalue, the wrapper holds the value, and if it was an lvalue the wrapper holds a reference to it. The class is supposed to overload, say, comparison operators and be used in this way:

template<typename T , typename U>
bool f( const T& lhs , const T& rhs )
{
    return wrapper( lhs ) == wrapper( rhs );
}

Or this:

int main()
{
    int a , b;
    bool flag = wrapper( a ) == wrapper( b ) || wrapper( a ) == wrapper( 2 );
}

My question is: Whats the best (efficient) way to implement such thing? That questions seems to broad, I mean:

  • How I define the member value? As T& for lvalues, and T for rvalues?
  • Is there any standard way to write this kind of universal (rvalue and lvalue) alias?
like image 837
Manu343726 Avatar asked Feb 10 '26 23:02

Manu343726


2 Answers

I would simply provide suitable conversion operators:

#include <utility>
#include <type_traits>

template <typename T> struct Wrapper
{
    static_assert(!std::is_reference<T>::value, "Do not use a reference type");

    using type = T;

    T value;
    Wrapper(T && t) : value(std::move(t)) {}
    Wrapper(T const & t) : value(t) {}

    operator T const & () const noexcept { return value; }
    operator T       & ()     & noexcept { return value; }
    operator T      && ()    && noexcept { return std::move(value); }

    // maybe some more CV variants...
};

template <typename U> struct Wrapper<U &>
{
    using type = U &;

    U & ref;
    Wrapper(U & u) : ref(u) {}

    operator U & () { return ref; }
};

I'd accompany this with a deducing function:

template <typename T> Wrapper<T> wrap(T && t)
{ return Wrapper<T>(std::forward<T>(t)); }

Example usage:

int n = 10;
bool b = wrap(n) == wrap(5 + 5)

The conversion operators allow you to use whatever operators are defined on the underlying type.

like image 59
Kerrek SB Avatar answered Feb 13 '26 04:02

Kerrek SB


I think Kerrek SB is on the right track by providing a specialization (got my +1 a long time ago), so each case is handled most efficiently.

The problem is you can't just add implicit conversion operators and if you want to provide your own operator overloads, things can become quite tricky.

The solution I came up with tries to deal with this by putting the information which case a certain variable is into a boolean template parameter. Here's the basic framework for the value_wrapper class:

template< typename T, bool >
class value_wrapper
{
private:
    T t_; // store a value

public:
    explicit value_wrapper( T&& t ) : t_( std::move( t ) ) {}
    const T& cref() const { return t_; }
};

template< typename T >
struct value_wrapper< T, true > // specialization for lvalue references
{
private:
    const T& t_; // store a reference

public:
    explicit value_wrapper( const T& t ) : t_( t ) {}
    const T& cref() const { return t_; }
};

The tricky part is the convenience method to wrap the values:

// needs a better name and needs to be moved into a "detail" or "impl" namespace
template< typename T >
using helper = value_wrapper< typename std::decay< T >::type, 
                              std::is_lvalue_reference< T >::value >;

template< typename T >
helper< T > wrap( T&& t )
{
    return helper< T >( std::forward< T >( t ) );
}

That way value_wrapper's first template parameter is always the decayed type, which makes everything easier now:

template< typename T, bool BL, bool BR >
bool operator==( const value_wrapper< T, BL >& lhs, const value_wrapper< T, BR >& rhs )
{
    return lhs.cref() == rhs.cref();
}

(obviously you want to implement them differently, but you can always access the stored values via cref() in a uniform way)

Live example

You might need to adjust this if you need non-constant access, etc. but I hope the above gets you started. If you need more help/ideas, feel free to ask :)

like image 33
Daniel Frey Avatar answered Feb 13 '26 06:02

Daniel Frey



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!