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: