-
Notifications
You must be signed in to change notification settings - Fork 216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[functional] [test] hana::partial re-forwarding (what were originally) rvalue references can bind to non-const lvalue reference overloads #239
Comments
Also, are there tests for reference forwarding? I did not find them in the test cases, which is why I did this, but I admittedly don't know exactly what's going on in the hana::partial tests - it appears to only be equality comparisons. Let me know if you want me to write more test cases. |
I'll tell you what happens, and then you can tell me whether you think it is broken or sensible.
This is, I think, the most flexible option (assuming we hold the arguments by value inside Do you see what's happenning now? If so, do you think this behavior is broken? I'm ambivalent about it, but I'm inclined to think that this is unintuitive to most people given your own confusion.
No. If you are willing to add some tests, please do so. The tests only make sure that |
As a note, the Fit library supports both ways, which is interesting. Here is an example: #include <fit/pack.h>
#include <fit/unpack.h>
#include <iostream>
struct bar {};
struct print_overload {
void operator()(bar& f) const {
std::cout << "bar&" << std::endl;
}
void operator()(const bar& f) const {
std::cout << "const bar&" << std::endl;
}
void operator()(bar&& f) const {
std::cout << "bar&&" << std::endl;
}
};
int main() {
auto p = fit::pack(bar{});
p(print_overload{});
fit::unpack(print_overload{})(p);
} Which outputs this:
As you can see calling For function adaptors, such as |
However, then, you might end up using a moved-from object when you probably do not expect it. Consider #include <fit/pack.h>
#include <fit/unpack.h>
#include <iostream>
struct print_overload {
void operator()(std::string& s) const {
std::cout << "std::string&: " << s << std::endl;
}
void operator()(std::string const& s) const {
std::cout << "std::string const&: " << s << std::endl;
}
void operator()(std::string&& s) const {
std::cout << "std::string&&: " << s << std::endl;
// actually move from s, as we expect might happen in real code
std::string ss = std::move(s);
}
};
int main() {
auto p = fit::pack(std::string{"abcdef"});
p(print_overload{});
p(print_overload{}); // uses a moved-from object
fit::unpack(print_overload{})(p);
} which outputs
The second call to |
@pfultz2 I also don't think we run the risk of providing too much granularity on this issue (even though I'm biased). @ldionne Side question: is the rvalue casting in I wrote this class template in my toy project for perfect forwarding the same reference from parameters I can't control, to arguments I can't control. (I guess we can call it "double forwarding"). An example/explanation can be found below this line. In order for hana::partial to do this, it would have to introspect on the argument types, which isn't possible for ambiugous cases (overloaded/templated callables) unless they are explicitly specified. Dealing with these situations is the main use case of CLBL. However, this class was not originally intended for partial function application - I only use it to "invisibly" forward between two sequence points. Prvalues would immediately lose scope and become invalid if this were used for persistent storage ("persistent double forwarding"), unless construction semantics were added. Perhaps the onus of reference preservation could be on the client, after forcing them to enable it explicitly. Another option would be to map some bit flags representing the value categories of the forwarding references at partial_t construction, and then dispatch on the flags when reforwarding, which I believe would still be done with constexpr-ness. I use that solution throughout CLBL. For persistent double forwarding, the onus of reference preservation would still have to be on the client. (Too bad we can't use Rust lifetimes). Not to toot my own horn on your issues thread - I just thought my trials-and-errors in this area might be of some help. |
It should move from a copy of |
Another idea: You could permute the possible qualifier/reference combinations on each the This would, however, probably be horribly slow to compile, so maybe not worth exploring. However, if you decay the types first, you don't have to permute over each argument independently - those permutations are, obviously, known by us at compile time. I imagine the type mapping would be the real kicker - I don't know what the space/time complexity is of Side question - I know you can use the term "qualifier" for const and volatile in all contexts, and I know you can have ref-qualified overloads of member functions, but can you call the (if my boss is somehow reading this: I'm billing these comments to weekly education time) |
To answer your question:
No. No single implementation of hana::partial can universally accommodate the 3 use cases I can see for it. I think the current implementation fits case number 1 of these. 2 and 3 have potential as sister classes or tagged specializations:
If a client thinks they need use case 2 or 3, they either have no idea what they are doing, or they have a very special use case (e.g. when they are forced to partially apply a std::unique_ptr argument). Since differentiating the 3 use cases will have to be explicit, the obvious choice is to leave the current and most common-sense implementation, 1, the default. I don't see the harm in adding support for 2 and 3, besides maintenance concerns. I'm interested in implementing them if you are indifferent. 3 is probably different enough to warrant a new name, such as |
It's to avoid instantiating
I don't know for sure. I'm sure I've used it before, but perhaps it was wrong. That might be a good question to ask on StackOverflow. Regarding allowing references in If someone is reading this, it might be interesting to see the chat discussion starting here. #include <boost/hana.hpp>
#include <functional>
#include <iostream>
#include <string>
#include <type_traits>
namespace hana = boost::hana;
template <typename T>
struct is_reference_wrapper {
static constexpr bool value = false;
};
template <typename T>
struct is_reference_wrapper<std::reference_wrapper<T>> {
static constexpr bool value = true;
};
struct unref_impl {
template <typename T, typename = void, typename = std::enable_if_t<
is_reference_wrapper<std::decay_t<T>>::value
>>
constexpr decltype(auto) operator()(T&& t) const {
return t.get();
}
template <typename T, typename = std::enable_if_t<
!is_reference_wrapper<std::decay_t<T>>::value
>>
constexpr decltype(auto) operator()(T&& t) const {
return static_cast<T&&>(t);
}
};
template <typename F>
constexpr auto unref(F&& f) {
return hana::on(static_cast<F&&>(f), unref_impl{});
}
int main() {
auto f = [](auto const& s) {
std::cout << s << std::endl;
};
std::string s = "abcdef";
auto p = hana::partial(unref(f), std::ref(s));
p();
} Basically, instead of changing Now, of course, since we lack a way to store rvalue references in a IDK, I'm just trying to find the least intrusive way (i.e. the most composable way) to add this functionality. I'm not sure it is to create a new |
As a note, However, with
Actually, it would only would be needed for |
Is there a difference between adding support for |
Of course, unwrapping ref wrappers when taking by value is just purely a convenience to the user, but no doubt could be beyond the scope of Hana(as its purpose is for heterogeneous sequences), especially since the user could just simply use |
Oops. Reference collapsing. Got it.
I think that's a nice idea. Clever, too. Automatic unwrapping of |
I'm going to close this as "wontfix" since this is out of scope for the library. With C++17, the need for functional combinators is rare (and in fact if it were not for constexpr lambdas, Hana would never have had them). When needed, Boost.Fit should be preferred as its goal is to solve that specific problem. |
Is this intended behavior? I was expecting it to bind to the const lvalue reference overload instead of the
&&
overload, since I assume you want apartial
object to remain idempotent - but I was not expecting the non-const lvalue reference overload.Repro - the side effects probably don't matter here, but I always like to have them when testing just to be sure, and so I can print the call counts whenever:
By the way, I studied your implementation of
partial
for a bit, and it's the coolest few lines of C++ I think I've ever read. The way you forward from the templated "secret" constructor back into the class template types so you can store the forwarded values is staggeringly creative. I've been trying to think of a way to do exactly that, and I never would've thought of that trick. It took me a few double takes to realize what you were doing.The text was updated successfully, but these errors were encountered: