c++copy-constructorrvalue-referenceperfect-forwardingmove-constructor

How to perfectly forward a universal reference that is either a const ref or a movable rvalue?


I have coded a lock-free and thread-safe ring queue with C++20, and it works so far. The only thing is not perfect that it has to have two enque() methods, one accepts a const reference to a lvalue as a argument, and the other accpects a reference to a rvalue, so as to move a rvalue into the queue instead of constructing again.

The before version of codes is following and merely a skeleton, to be simplified:

template <typename T>
class RingQue_t {
public:
    explicit RingQue_t( size_t capacity );
    ~RingQue_t();
    bool deque( T& dest_ ) { return true; };

    // If we offer a const ref, the compiler will select this one
    bool enque( const T& src_ ) {
        // a lot of same codes goes here for a same logic...

        new( _buff + _tail ) T( src_ );
    };

    // If we offer a rvalue ref, the compiler will select this one
    bool enque( T& src_ ) {
        // a lot of same codes goes here for a same logic...

        new( _buff + _tail ) T( std::move( src_ ) );
    };

protected:
    T*  _buff = nullptr;
};

I'm trying to merge these two methods into one, and had read some docs and examples about std::forward, but I still can NOT use it correctly. This is my expecting:

template <typename T>
class RingQue_t {
public:
    template<typename U>
    bool enque( U&& src_ ) {
        // new( _buff + _tail ) T( src_ );
        // new( _buff + _tail ) T( std::move( src_ ) );
        // new( _buff + _tail ) T( std::forward<T>( src_ ) );

        std::allocator<T> allc;
        allc.construct( _buff + _tail, std::forward<T>( src_ ) );
        return true;
    };
};

// testing
const std::string s0 { "s0" };
RingQue_t<std::string> str_que( 16 );
str_que.enque( std::string { "s1" } ); // works
str_que.enque( s0 ); // can not pass the compiling.

All solutions in comments had been tried, none works. I always receive a error msg:

binding reference of type ‘std::remove_referencestd::__cxx11::basic_string<char >::type&’ {aka ‘std::__cxx11::basic_string&’} to ‘const std::__cxx11::basic_string’ discards qualifiers

What is the correct way to use std::forward?


Solution

  • The proplem is related to the the fact, that enque() does not properly forward the constness of the argument. This is because U is deduced as const T&, but after forwarding using std::forward<T>() this constness is lost. In order to fix this simply replace std::forward<T>() with std::forward<U>()

    Also note that std::allocator<T>::construct is deprecated in c++17, instead use std::allocator_traits::construct