Boost logo

Boost :

Subject: Re: [boost] [operators] The Dangling Reference Polarization
From: Daniel Frey (d.frey_at_[hidden])
Date: 2013-05-04 06:49:26


On 04.05.2013, at 08:52, Dave Abrahams <dave_at_[hidden]> wrote:

> on Thu Apr 25 2013, Daniel Frey <d.frey-AT-gmx.de> wrote:
>
>> If an operator returns an rvalue reference (T&&), it is safe for all
>> use-cases except binding the result to an rvalue- or
>> const-lvalue-reference:
>>
>> T r = a + b + c; // no problem
>> const T r = a + b + c; // no problem
>>
>> T& r = a + b + c; // compile-time error
>>
>> const T& r = a + b + c; // problem (P1)
>> T&& r = a + b + c; // problem (P2)
>>
>> It seems that this is consistent behavior across at least GCC, Clang
>> and VC++. If a compiler shows a different behavior, its likely a
>> compiler-bug.
>
> I believe Howard and I basically came to the same conclusion about
> returning rvalue references: Just Don't Do It. Any potential speedups
> are vastly outweighed by the safety costs. Return by value; it will
> move anyway :-)

I agree with return-by-value now, but the example that I needed to see the importance was the range-based for statement which implicitly binds a reference. I think that in some cases return-by-rvalue-reference might still be useful, but the user needs to explicitly opt-in on it and the decision should be made on a per-class case, not just by a #define. Anyways, the remaining question is whether or not pass-by-value on the parameter side could improve some cases. I attached my current test code at the end of this message. Some observations so far:

1) return-by-value and return-by-rvalue-reference are identical except for the return type, its the same set of overloads and the same implementation.
2) All three techniques differ only in the number of move-operations (and hence in the number of temporaries created). Copies and the number of called operator=-overloads (lvalue and rvalue) is the same.
3) I can't find a single case where today's compilers can benefit from pass-by-value. I'd be really interested if you could come up with an example expression where pass-by-value can lead to better code.

Best regards, Daniel

#include <iostream>
#include <utility>

unsigned copies, moves, lvalue, rvalue;

void reset() { copies = moves = lvalue = rvalue = 0; }

void print( const char* name )
{
  std::cout << copies << " copies, "
            << moves << " moves, "
            << lvalue << " lvalue ops, "
            << rvalue << " rvalue ops, "
            << "T r = " << name << std::endl;
}

void check( const unsigned e1, const unsigned e2, const unsigned e3, const unsigned e4 )
{
  if( copies != e1 || moves != e2 || lvalue != e3 || rvalue != e4 ) {
    std::cout << e1 << " "
              << e2 << " "
              << e3 << " "
              << e4 << " "
              << "(expected)" << std::endl;
  }
}

struct T
{
  T() {}

  T( const T& ) { ++copies; }
  T( T&& ) { ++moves; }

  T& operator+=( const T& ) { ++lvalue; return *this; }
  T& operator+=( T&& ) { ++rvalue; return *this; }

  T& operator-=( const T& ) { ++lvalue; return *this; }
  T& operator-=( T&& ) { ++rvalue; return *this; }
};

#define STANDARD
// #define RETURN_RVALUE_REFERENCE
// #define PASS_BY_VALUE

// operator+ is commutative
#ifdef STANDARD
T operator+( const T& lhs, const T& rhs ) { T nrv( lhs ); nrv += rhs; return nrv; }
T operator+( T&& lhs, const T& rhs ) { lhs += rhs; return std::move( lhs ); }
T operator+( const T& lhs, T&& rhs ) { rhs += std::move( lhs ); return std::move( rhs ); }
T operator+( T&& lhs, T&& rhs ) { lhs += std::move( rhs ); return std::move( lhs ); }
#endif
#ifdef RETURN_RVALUE_REFERENCE
T operator+( const T& lhs, const T& rhs ) { T nrv( lhs ); nrv += rhs; return nrv; }
T&& operator+( T&& lhs, const T& rhs ) { lhs += rhs; return std::move( lhs ); }
T&& operator+( const T& lhs, T&& rhs ) { rhs += std::move( lhs ); return std::move( rhs ); }
T&& operator+( T&& lhs, T&& rhs ) { lhs += std::move( rhs ); return std::move( lhs ); }
#endif
#ifdef PASS_BY_VALUE
T operator+( T lhs, const T& rhs ) { lhs += rhs; return lhs; }
T operator+( const T& lhs, T&& rhs ) { rhs += std::move( lhs ); return std::move( rhs ); }
T operator+( T&& lhs, T&& rhs ) { lhs += std::move( rhs ); return std::move( lhs ); }
#endif

// operator- is non-commutative
#ifdef STANDARD
T operator-( const T& lhs, const T& rhs ) { T nrv( lhs ); nrv -= rhs; return nrv; }
T operator-( T&& lhs, const T& rhs ) { lhs -= rhs; return std::move( lhs ); }
T operator-( const T& lhs, T&& rhs ) { T nrv( lhs ); nrv -= std::move( rhs ); return nrv; }
T operator-( T&& lhs, T&& rhs ) { lhs -= std::move( rhs ); return std::move( lhs ); }
#endif
#ifdef RETURN_RVALUE_REFERENCE
T operator-( const T& lhs, const T& rhs ) { T nrv( lhs ); nrv -= rhs; return nrv; }
T&& operator-( T&& lhs, const T& rhs ) { lhs -= rhs; return std::move( lhs ); }
T operator-( const T& lhs, T&& rhs ) { T nrv( lhs ); nrv -= std::move( rhs ); return nrv; }
T&& operator-( T&& lhs, T&& rhs ) { lhs -= std::move( rhs ); return std::move( lhs ); }
#endif
#ifdef PASS_BY_VALUE
T operator-( T lhs, const T& rhs ) { lhs -= rhs; return lhs; }
T operator-( const T& lhs, T&& rhs ) { T nrv( lhs ); nrv -= std::move( rhs ); return nrv; }
T operator-( T&& lhs, T&& rhs ) { lhs -= std::move( rhs ); return std::move( lhs ); }
#endif

#define TEST( e1, e2, e3, e4, X ) do { reset(); T r = X; (void)r; print( #X ); check( e1, e2, e3, e4 ); } while( false )

int main()
{
  T t;

  // expected talues refer to STANDARD
  // RETURN_RTALUE_REFERENCE optimizes some cases
  // PASS_BY_TALUE pessimized some cases

  TEST( 0, 1, 0, 1, T() - T() ); // 1
  TEST( 0, 1, 1, 0, T() - t ); // 2
  TEST( 0, 1, 1, 0, t + T() ); // 3.1
  TEST( 1, 0, 0, 1, t - T() ); // 3.2
  TEST( 1, 0, 1, 0, t - t ); // 4

  TEST( 1, 1, 2, 0, t - t - t ); // 4 + 2
  TEST( 1, 1, 2, 0, t + ( t + t ) ); // 4 + 3.1
  TEST( 2, 0, 1, 1, t - ( t - t ) ); // 4 + 3.2
  TEST( 2, 1, 2, 1, ( t - t ) - ( t - t ) ); // 4 + 4 + 1

  TEST( 1, 2, 3, 0, t - t - t - t ); // 4 + 2 + 2
  TEST( 1, 3, 4, 0, t - t - t - t - t ); // 4 + 2 + 2 + 2
}


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk