A simple example demonstrating how std::forward works in C++

For example, std::forward comes to play if I implement a template container with two ‘insert‘ function overloads that take the parameters of type const T & and T && respectively. I implement all the private insertion logic with T&&, but when I need to know the original value type passed to ‘insert‘ method I use std::forward as shown in the example below:

#include <iostream>
#include <string>

class Value
{
public:

    Value(const char * sz) : m_s(sz)
    {
    }

    Value(const Value & other) : m_s(other.m_s)
    {
        std::cout << "copy constructor: " << m_s << std::endl;
    }

    Value(Value && other) : m_s(std::move(other.m_s))
    {
        std::cout << "move constructor: " << m_s << std::endl;
    }

    const std::string & ToString() const
    {
        return m_s;
    }

private:

    std::string m_s;
};
template<class T> 
class Container
{
public:

    using value_type = T;
    
    void insert(const value_type& value)
    {
        std::cout << "inserting lvalue: " << value.ToString() << std::endl;
        insert_impl(value);
    }

    void insert(value_type&& value)
    {
        std::cout << "inserting rvalue: " << value.ToString() << std::endl;
        insert_impl(std::move(value));
    }
    
    ~Container()
    {
        delete pVal;
    }

private:

    template <typename V>
    void insert_impl(V && value)
    {
        //There can be a complicated logic of inserting an element to the container.
        pVal = new Value(std::forward<V>(value));
    }
    
    T * pVal = nullptr;
};

int main()
{
    Container<Value> container;
    
    Value lval = "abc";
    container.insert(lval); //const Value & && => const Value &
    Value rval = "xyz";
    container.insert(std::move(rval)); //Value && && => Value &&
    container.insert(Value("uvw"));    //Value &&

    std::cout << "lval: '" << lval.ToString() << "'" << std::endl;
    std::cout << "rval: '" << rval.ToString() << "'" << std::endl;

    return 0;
}

The output is:

inserting lvalue: abc
copy constructor: abc
inserting rvalue: xyz
move constructor: xyz
inserting rvalue: uvw
move constructor: uvw
lval: 'abc'
rval: ''

As you can see, the value is either copied or moved in insert_impl function.

Use the following command to compile the code with MSVC2017:

cl /std:c++17 /EHsc a.cpp

The definition of forwarding reference

MSVC source code with omited [[nodiscard]]:

template <class>
_INLINE_VAR constexpr bool is_lvalue_reference_v = false; // determine whether type argument is an lvalue reference

template <class _Ty>
_INLINE_VAR constexpr bool is_lvalue_reference_v<_Ty&> = true;

template <class _Ty>
struct is_lvalue_reference : bool_constant<is_lvalue_reference_v<_Ty>> {};

template <class>
_INLINE_VAR constexpr bool is_rvalue_reference_v = false; // determine whether type argument is an rvalue reference

template <class _Ty>
_INLINE_VAR constexpr bool is_rvalue_reference_v<_Ty&&> = true;

template <class _Ty>
struct is_rvalue_reference : bool_constant<is_rvalue_reference_v<_Ty>> {};

template <class>
_INLINE_VAR constexpr bool is_reference_v = false; // determine whether type argument is a reference

template <class _Ty>
_INLINE_VAR constexpr bool is_reference_v<_Ty&> = true;

template <class _Ty>
_INLINE_VAR constexpr bool is_reference_v<_Ty&&> = true;

template <class _Ty>
struct is_reference : bool_constant<is_reference_v<_Ty>> {};

// forward an lvalue as either an lvalue or an rvalue
template <class _Ty>
constexpr _Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept
{
    return static_cast<_Ty&&>(_Arg);
}

// forward an rvalue as an rvalue
template <class _Ty>
constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept
{
    static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
    return static_cast<_Ty&&>(_Arg);
}

// forward _Arg as movable
template <class _Ty>
constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept
{
    return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}

Reference collapsing rules:

Links:

Leave a Reply

Your email address will not be published. Required fields are marked *