patterncppMinor
Elementwise perfect forwarding of a member range
Viewed 0 times
rangeperfectelementwisememberforwarding
Problem
Perfect forwarding is usually done with
for some function
...But what if
Here,
Note that simply writing
won't work as
Note that I use
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 problemtemplate
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.
-
You could avoid some of the pre-C++14 verbosity by using
-
How do you know whether
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
(If so, I haven't figured out the syntax to support it in your sample with
- Like
std::forwardyou should consider makingforward_asaconstexprfunction.
-
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; perhapsforward_if(u)or evenmove_if_rvalue(u)would help this not look like it returns an expression of aT-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.