Perfect forwarding is usually done with std::forward
. E.g.,
template<typename T>
void foo( T&& t ) {
bar(std::forward<T>(t));
}
for some function bar()
. Sometimes, only a member of t
, say t.x
, needs to be forwarded. This is also a solved problem
template<typename T>
void foo( T&& t ) {
bar(std::forward<T>(t).x);
}
...But what if t
has a range member, say t.range
, and bar
takes an element from said range? E.g.,
template<typename T>
void foo( T&& t ) {
for (auto& r : t.range)
bar(???(r));
}
Here, ???
is a placeholder for a cast which forwards the type of r
as the type of t
itself would be forwarded.
Note that simply writing
template<typename T>
void foo( T&& t ) {
for (auto&& r : std::forward<T>(t).range)
bar(r);
}
won't work as r
is still named and will always pass by value or l-value reference but never by r-value reference. To solve this I introduce forward_as<T>
////////////////////////////////////////////////////////////////////////////////
/// Forward As
///
/// Forwards u of type U as T would be forwarded. I.e., u is forwarded
/// like some t would be forwarded by std::forward<T>(t). The cv-qualifiers of
/// U are preserved. E.g., forward_as<T, std::string>(u) casts u to:
///
/// (1) std::string& if T is int&
/// (2) std::string& if T is const int&
/// (3) std::string&& if T is int&&
///
/// Analogously, forward_as<T, const std::string>(u) casts u to:
///
/// (1) const std::string& if T is int&
/// (2) const std::string& if T is const int&
/// (3) const std::string&& if T is int&&
///
////////////////////////////////////////////////////////////////////////////////
template<typename T, typename U>
auto forward_as( U&& u ) -> std::conditional_t<
std::is_reference<T>::value,
std::remove_reference_t<U>&,
std::remove_reference_t<U>&&>
{
return static_cast<std::conditional_t<
std::is_reference<T>::value,
std::remove_reference_t<U>&,
std::remove_reference_t<U>&&>>(u);
}
Note that I use auto
with a trailing return type. This is because my compiler is old. It should work just fine with decltype(auto)
on a C++14 compliant compiler. Also note that I've chosen to preserve cv-qualifiers of the given type.
Now the problem from before can be solved by
template<typename T>
void foo( T&& t ) {
for (auto& r : t.range)
bar(forward_as<T>(r));
}
Direct member forwarding is also possible using forward_as<T>(t.x)
.
Have I missed any edge cases? Is there an alternative to all of this? Any suggestions for interface/naming/style improvements?
Edit: I've made a code sample. The syntax is slightly more verbose since the ideone compiler only supports C++11. The underlying principle is the same.