Question

I'm working on a simple wrapper template class that logs when special member functions are called. These functions cannot be defaulted since they perform additional logging related tasks.

template <typename T>
struct logger {
    logger(T const& value) : value_(value) { /*...log...*/ }
    logger(T&& value) : value_(std::move(value)) { /*...log...*/ }
    logger(logger const& other) : value_(other.value_) { /*...log...*/ }
    logger(logger&& other) : value_(std::move(other.value_)) { /*...log...*/ }

    T value_;
};

Unfortunately, when the wrapped type is an rvalue-reference the copy-constructor fails to compile with the following error message:

error: cannot bind ‘int’ lvalue to ‘int&&’

The reason is that an implicit copy constructor will behave somewhat different for an rvalue-reference member:

[class.copy 12.8/15] The implicitly-defined copy/move constructor for a non-union class X performs a memberwise copy/move of its bases and members. [...] Let x be either the parameter of the constructor or, for the move constructor, an xvalue referring to the parameter. Each base or non-static data member is copied/moved in the manner appropriate to its type:

  • if the member is an array, each element is direct-initialized with the corresponding subobject of x;
  • if a member m has rvalue reference type T&&, it is direct-initialized with static_cast<T&&>(x.m);
  • otherwise, the base or member is direct-initialized with the corresponding base or member of x.

Which brings me to my question: how does one write a generic copy-constructor that behaves as an implicitly defined copy-constructor, even when working with rvalue-references as members.

For this particular case, I could add an additional specialization for rvalue-references. However, I'm looking for a generic solution that doesn't restrict to single members and does not introduce code duplication.

Was it helpful?

Solution

Here be dragons.

logger(logger const& other) : value_(other.value_)

The expression other.value_ is an lvalue of type T const, e.g. int&, int&& or int const.

  1. If T == int&&, you need to do a move, as the expression is an lvalue. The move is equivalent to a static_cast<int&&>, so you could do the static_cast directly as well.

  2. If T == int&, no cast is required.

  3. If T == int, no cast is required.

For a copy ctor defined as:

logger(logger const& other) : value_(static_cast<T>(other.value_)) {/*...*/}

Applied to the third case, this is defined as the introduction of a temporary, and could result in an additional copy/move, although I think it can&will be elided.

A solution without relying on the copy/move elision is to introduce a weird_cast, that yields the desired type in any case:

#include <type_traits>

template<class T, class U>
typename std::enable_if<std::is_reference<T>{}, T>::type
weird_cast(U& p)
{
    return static_cast<T>(p);
}

template<class T, class U>
typename std::enable_if<not std::is_reference<T>{}, T const&>::type
weird_cast(U const& p)
{
    return p;
}

int main()
{
    int           o = 42;
    int &        lo = o;
    int &&       ro = std::move(o);
    int const   lco = o;

    int&& r = weird_cast<int&&>(ro);
    int&  l = weird_cast<int& >(lo);
    int   d = weird_cast<int  >(lco);
}

This is similar to std::forward, but also supports "forwarding" non-reference types.


Where are the dragons?

[class.copy]/11 specifies:

A defaulted copy/move constructor for a class X is defined as deleted if X has:

  • [...]
  • for the copy constructor, a non-static data member of rvalue reference type
  • [...]

An rvalue reference is typically bound to an xvalue or prvalue, i.e. to an expression referring to an object that is "near the end of its lifetime". As lifetime doesn't get extended through function boundaries, it would be error prone to allow such a "copying".

OTHER TIPS

You could write a specialization for rvalue-references:

template<typename T>
struct logger<T&&>{
  ...
};

But really I don't think you want logger::_value to be a rvalue reference...

Edit

Although I feel this isn't a bad solution, as it's a GENERAL workaround for ALL rvalue references, here's another option without literal specialization:

template<typename TT>
struct logger{
  typedef typename rvalue_remover<TT>::value T;
  //your previous code here
};

Where rvalue_remover is something like this:

template<typename T>struct rvalue_remover{typedef T value;};
template<typename T>struct rvalue_remover<T&&>{typedef T value;};

I'm pretty sure this is already defined in c++11, but I don't have it installed here so I don't remember the name.

Edit2

Ah! found it! It's called std::remove_reference<T>::type, and declared in #include <type_traits>

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top