3

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?
Manu343726
  • 13,969
  • 4
  • 40
  • 75
  • 1
    The more I think about it, the less I understand what such a wrapper class is trying to achieve. Can you elaborate on the use-case? – Daniel Frey Jan 04 '14 at 23:46
  • @DanielFrey among other things, I was thinking to write a set of floating-point aware comparison functions, and I would like to use comparison operators. I don't like the "Java style" `compare_to(a,b)`, I prefer operators overloading. – Manu343726 Jan 05 '14 at 00:00
  • 1
    Interesting. In that case you might want to avoid implicit conversion operators and only have them marked as `explicit` or via explicit getter functions. And you then have to overload the operators yourself, probably using [Boost.Operators](http://www.boost.org/doc/libs/1_53_0/libs/utility/operators.htm) or my [**df.operators**](https://github.com/d-frey/operators/) to help with that. – Daniel Frey Jan 05 '14 at 00:04
  • @DanielFrey I always (Almost when I can) use boost::operators to automatize operators overloading :) So what approach I should use to hold the original values? – Manu343726 Jan 05 '14 at 00:07
  • I'll think about it tomorrow, I'm really tired and need to sleep now. – Daniel Frey Jan 05 '14 at 00:10
  • @DanielFrey thanks for your help anyway. Good night. – Manu343726 Jan 05 '14 at 00:39
  • @Manu343726 I simply don't get the whole purpose of this. How is it any better to write `if(wrap(num)==wrap(1.1)){` then `if(equals(num,1.1)){` or `if(equals(num,1.1,threshold)){`? By the way, how would you easily change the default threshold for equality in `operator==` at the calling site? I most likely miss some important point... – Ali Jan 05 '14 at 13:45
  • @Ali I'll leave it to Manu343726 to answer this, but just because i don't see the real-world use-case or wether or not I agree with the syntax doesn't necessarily entitle me to judge. And SO is sometimes not the right format to get an idea about the big picture, so I just tried to help out with some ideas. OP is ultimately responsible for whether this code gets used or not :) – Daniel Frey Jan 05 '14 at 13:50
  • @DanielFrey Fair enough. After all, you didn't ask the question :) – Ali Jan 05 '14 at 13:55
  • @Ali besides I prefer the operator sintax over the function, the comparison is just an example. I was just wondering which is the best way to write a wrapper/alias class for values, and if it works, how to redefine the behavior of that values. Polymorphism is not the way to do because I was looking for a solution which works with basic types too. As Daniel said, SO quesions format is not the best to show the complete scope of a problem/solution. – Manu343726 Jan 05 '14 at 14:33
  • @Manu343726 All right then. Good luck with whatever it is you are doing! – Ali Jan 05 '14 at 14:36

2 Answers2

8

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.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 1
    I don't understand the second & in this line `operator T & () & noexcept { return value; }` what c++ info should I look for , what it is related to ? – Stephane Rolland Jan 04 '14 at 23:47
  • @StephaneRolland: It's the lvalue qualifier. New in C++11, pairs up with the subsequent rvalue qualifier. This allows you to overload on whether the object itself is an lvalue or an rvalue. – Kerrek SB Jan 04 '14 at 23:48
  • what problem does it protect against ? what does it enforce ? – Stephane Rolland Jan 04 '14 at 23:52
  • 1
    @StephaneRolland: It allows you to use temporary Wrapper objects efficiently. – Kerrek SB Jan 04 '14 at 23:55
  • Even if this class is, from my noob point of view, amazing (I have no idea about that qualifiers before this, and I will consider them in the future for conversion ops), this does not solve my problem, because what I need is to override the behavior of the operators, not to wrap it. Regarding on your class, why if I need to use a reference? – Manu343726 Jan 05 '14 at 00:04
2

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 :)

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • Thanks for your answer, I think this is the solution. One question: Do you think the compiler is cappable of inline all (Or almost most of them) `value_wrapper` machinery, and generate only the code for the comparison? I mean, if I have a comparison overload for `value_wrapper` like this: `bool operator==( ... ) { return compare( lhs.cref() , lhs.cref() ); }`, and I use it as in my examples ( `bool flag = wrap( a ) == wrap( b);`), the compiler is capable of generate code not much more complicated than `bool flag = compare( a , b );`? – Manu343726 Jan 05 '14 at 11:56
  • @Manu343726 Not if you take a copy (probably moved to), the compiler wouldn't even be allowed to remove it unless it could **prove** that there are no side-effects (which is quite tricky in some cases). But also note that you might not need those copies anyways since even temporaries are only destroyed at the end of the full expression they occur in. If all uses look like `wrap(a)==wrap(f())` it doesn't matter if `f()` returned a temporary as this temporary (and references to it) are still valid when `operator==` is evaluated. – Daniel Frey Jan 05 '14 at 12:00
  • Thanks. I have compiled it using GCC Explorer (GCC4.8.1), and it surprised me because even with `-O` the compiler inlines everithing (the value_wrapper operator= body included) both for lvalues and rvalues. – Manu343726 Jan 05 '14 at 12:13
  • Sorry, one more question: Why is decaying neccesary? For arrays? To store a pointer to the array instead of the array itself? – Manu343726 Jan 05 '14 at 13:02
  • @Manu343726 Mostly to get rid of the reference itself `T&` -> `T`, otherwise the operator overload needs to be way more complicated. With the decayed type, the overload works if `T` is identical for both `lhs` and `rhs`, the boolean is ignored. Without decaying first, you'd need 4 overloads instead of 1 or some other complicated way to handle the valid combinations (`T,T`, `T,T&`, `T&,T`, `T&,T&`). That said, it might not be the best options if you also want to support arrays as well, consider `std::remove_reference` as a replacement. – Daniel Frey Jan 05 '14 at 13:05