-
Notifications
You must be signed in to change notification settings - Fork 172
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
Prefer T(...) over T{...} when they behave differently in construct, makeUnique and makeShared #225
base: master
Are you sure you want to change the base?
Conversation
...sadly, this does not compile on Clang 3.5 :/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can try some different version of SFINAE (default template parameter or default non-type template parameter). As a last resort, you can try tag dispatch with something like std::true_type
and std::false_type
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Besides the couple of improvements suggested, this looks great!
@@ -13,7 +13,7 @@ struct ApplyTupleImpl; | |||
|
|||
template <typename... Ts, std::size_t... Indexes, typename Callable> | |||
struct ApplyTupleImpl<std::tuple<Ts...>, Callable, IndexSequence<Indexes...>> { | |||
using ReturnType = typename std::result_of<Callable(Ts &&...)>::type; | |||
using ReturnType = typename std::result_of<Callable&&(Ts &&...)>::type; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clever fix of the compilation issue. I guess the non-SFINAE friendliness of result_of
, coupled with the fact that it is not defined for function types (but fine for function references; source: https://en.cppreference.com/w/cpp/types/result_of#Notes ) cause the compilation error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm afraid I'm not following the reasoning for making this an rvalue here. It's not called as an rvalue below so I don't see how this is correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::result_of<F(Args...)>::type
does not work if F is a function (but works if F is a reference to function).
Here's an example:
int foo(int x) { return x; }
using T = std::result_of<decltype (foo)(int)>::type;
^ fails with
./foo.cpp:34:39: error: function cannot return function type 'decltype(foo)' (aka 'int (int)')
...however, this works:
using T = std::result_of<decltype (&foo)(int)>::type;
When I do:
template <typename T, typename... Args>
Gen<T> construct(Gen<Args>... gens) {
return gen::map(gen::tuple(std::move(gens)...),
[](std::tuple<Args...> &&argsTuple) {
return rc::detail::applyTuple(
std::move(argsTuple),
[](Args &&... args) { return T{std::move(args)...}; });
std::forward<std::tuple<Args...>>(argsTuple),
detail::construct<T, Args...>
);
});
}
...it fails when it tries to do result_of<decltype(detail::construct<T, Args...>)(Args...)
, while I want it to do result_of<decltype(&detail::construct<T, Args...>)(Args...)
.
https://en.cppreference.com/w/cpp/types/result_of#Notes suggests using result_of<F&&(Args...)>
instead result_of<F(Args...)>
to avoid this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course, one might also wonder whether putting &
in front of detail::construct<T, Args...>
will work. Unfortunately, I never managed to check it since it crashes Clang 3.5.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because of the limitations of result_of
I think it is better to keep the &&
. Technically speaking this is not a r-value reference, but does reference collapsing based on the value category of Callable
so there should be no substantial changes.
template<typename T, typename... Args> | ||
typename std::enable_if<!std::is_constructible<T, Args...>::value, std::shared_ptr<T>>::type | ||
makeShared(Args &&... args) { | ||
return std::shared_ptr<T>(new T{std::forward<Args>(args)...}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This cannot work the same way as above (using std::make_shared
) but an alternative would be to do:
return std::make_shared(T{std::forward<Args>(args)...});
This would do one allocation but it would have to move the aggregate in the memory (which would likely be a copy since aggregates are mostly simple data structs). I believe it would still be more efficient than what we have now (but not as perfect as the other case above).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will require checks for std::is_copy_constructible
and std::is_move_constructible
, since it's possible to have aggregates that are not copyable/movable. @emil-e, do you think it's worth adding extra overloads?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I think it's worth it. This solution is not the absolutely most performant but it's the most compatible in combination with relative simplicity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that it might be better to not strive for absolute generality for the sake of simplicity. And yeah, it will probably be completely insignificant in the larger picture.
...in gen::construct and gen::makeUnique. Also, add support for aggregate initialization in gen::makeShared and tests for all of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for not getting back earlier, life gets in the way!
This looks great, but I'm a bit confused about the return type stuff so if you could clarify that I would appreciate it.
@@ -13,7 +13,7 @@ struct ApplyTupleImpl; | |||
|
|||
template <typename... Ts, std::size_t... Indexes, typename Callable> | |||
struct ApplyTupleImpl<std::tuple<Ts...>, Callable, IndexSequence<Indexes...>> { | |||
using ReturnType = typename std::result_of<Callable(Ts &&...)>::type; | |||
using ReturnType = typename std::result_of<Callable&&(Ts &&...)>::type; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm afraid I'm not following the reasoning for making this an rvalue here. It's not called as an rvalue below so I don't see how this is correct?
template<typename T, typename... Args> | ||
typename std::enable_if<!std::is_constructible<T, Args...>::value, std::shared_ptr<T>>::type | ||
makeShared(Args &&... args) { | ||
return std::shared_ptr<T>(new T{std::forward<Args>(args)...}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I think it's worth it. This solution is not the absolutely most performant but it's the most compatible in combination with relative simplicity.
@Thorius reviewed my earlier PR (#224) and brought to my attention that it changed the behaviour of
gen::construct<T>(...)
when T has a constructor that accepts an initializer list. One such example:I think it's sensible to expect
gen::construct
,gen::makeUnique
andgen::makeShared
to act like the corresponding factory functions from the standard library when such ambiguity arises. After some searching I found this paper: http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4462.html, describing one possible way to make aggregates work without breaking existing behaviour.This PR makes
gen::construct
,gen::makeUnique
andgen::makeShared
check whether objectis_constructible
, and only fall back to brace initialization when it's not. I also added tests for each of them.