HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Elementwise perfect forwarding of a member range

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
rangeperfectelementwisememberforwarding

Problem

Perfect forwarding is usually done with std::forward. E.g.,

template
void foo( T&& t ) {
    bar(std::forward(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
void foo( T&& t ) {
    bar(std::forward(t).x);
}


...But what if t has a range member, say t.range, and bar takes an element from said range? E.g.,

template
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
void foo( T&& t ) {
    for (auto&& r : std::forward(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

////////////////////////////////////////////////////////////////////////////////
/// 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). The cv-qualifiers of 
/// U are preserved. E.g., forward_as(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(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
auto forward_as( U&& u ) -> std::conditional_t::value, 
        std::remove_reference_t&, 
        std::remove_reference_t&&>
{
    return static_cast::value, 
        std::remove_reference_t&, 
        std::remove_reference_t&&>>(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++

Solution

I'll start with some direct review of what you've implemented. After the bullets, however, I go into some concerns about whether this should be used. Thanks for the interesting idea; reviewing it made me re-examine several things I thought I already knew.

  • Like std::forward you should consider making forward_as a constexpr function.



-
You could avoid some of the pre-C++14 verbosity by using decltype inside forward_as (although the auto return type sounds better still):

return static_cast(u))>(u);


-
How do you know whether for (auto&& r : t.range) will iterate over objects owned by t?

  • Admittedly a bikeshed point, I don't like the way the name forward_as(u) reads; perhaps forward_if(u) or even move_if_rvalue(u) would help this not look like it returns an expression of a T-based type.



However like Kerrek SB's comment on the referenced question, I worry about the state of the main object when forwarding or moving its members. What are the rules for doing this safely? Your examples seem to show forwarding only a single member, but what are the rules when forwarding different parts of the same object? What are the code review guidelines to ensure that problems are not introduced in later edits?

To sum up, I mistrust this. It might just be my own lack of familiarity with such a use case. But it also reflects an uncertainty that optimizing for move is likely to be worth it in these cases. If this is actually a bottleneck or potential bottleneck in the code, are there better alternatives? Does bar even overload on r-value types? Should foo receive t.range instead of t, or will std::for_each be able to generate r-values automatically:

std::for_each(std::begin(t.range), std::end(t.range), bar)


(If so, I haven't figured out the syntax to support it in your sample with bar as a function template. Using instead a struct with overloaded operator(), it appears to suggest we shouldn't assume the range contains r-values.)

Code Snippets

return static_cast<decltype(forward_as<T, U>(u))>(u);
std::for_each(std::begin(t.range), std::end(t.range), bar)

Context

StackExchange Code Review Q#68651, answer score: 4

Revisions (0)

No revisions yet.