Naios / function2

Improved and configurable drop-in replacement to std::function that supports move only types, multiple overloads and more
http://naios.github.io/function2
Boost Software License 1.0
545 stars 46 forks source link

Use strongly typed configuration options instead of multiple bools #45

Open jk-jeon opened 3 years ago

jk-jeon commented 3 years ago

@Naios Here are some suggestions about fu2::function_base.

  1. There are too many boolean template parameters. It's hard to imagine what specific combination of policies are given if we just write
    template<typename Arg>
    using my_consumer = fu2::function_base<true, false, fu2::capacity_fixed<100U>,
                                       true, false, void(Arg)&&>;

    In my opinion, better alternatives might be:

    • Use separate enum types for each policy parameter, or
    • Change policy parameters from non-type parameters to type parameters, and define new empty type for each policy.

The second approach might require more template metaprogramming, but it's open-ended (users can add more options at their will) while the enum approach is closed-ended. This might be either pros or cons, though.

I recommend the enum approach, and I think there is virtually no downside of it compared to the boolean approach, except that the implementation might be a bit more verbose.

  1. This one is about README. I'm not sure what HasStrongExceptGuarantee is meant to mean. I mean, I know what strong exception guarantee means in general, but I think it might be better to explicitly say (1) where are the places fu2::function_base is guarding against exceptions when HasStrongExceptGuarantee is set to be true, and (2) exactly what invariants are preserved. I assumed these are about copy assignment, is that correct?

Thanks!

Naios commented 3 years ago

Yes, you are right about the bool configuration. Using a strongly typed enum would prevent misconfiguration and would be an improvement. However, changing this leads to a breaking change that I could do earliest at the next major version bump.

Currently, the HasStrongExceptGuarantee only propagates noexcept from erased objects to the signature of the move construct and move assign of the type erasure such that containers can optimize their behavior based on that. There are no guarantees made (yet) about the strong exception guarantees of the type erasure (function_base) itself.

jk-jeon commented 3 years ago

Yes, you are right about the bool configuration. Using a strongly typed enum would prevent misconfiguration and would be an improvement. However, changing this leads to a breaking change that I could do earliest at the next major version bump.

Sounds great.

Currently, the HasStrongExceptGuarantee only propagates noexcept from erased objects to the signature of the move construct and move assign of the type erasure such that containers can optimize their behavior based on that. There are no guarantees made (yet) about the strong exception guarantees of the type erasure (function_base) itself.

Hmm. I'm not sure if I got you correctly. So my current understanding is that, if the underlying object's move constructor/assignment are not noexcept, and if HasStrongExceptGuarantee is set to be true, and if SBO is in action, then the move actually behaves as if it were copy. Is this correct?

Naios commented 3 years ago

https://en.cppreference.com/w/cpp/language/exceptions

1) Nothrow (or nofail) exception guarantee -- the function never throws exceptions. Nothrow (errors are reported by other means or concealed) is expected of destructors and other functions that may be called during stack unwinding. The destructors are noexcept by default. (since C++11) Nofail (the function always succeeds) is expected of swaps, move constructors, and other functions used by those that provide strong exception guarantee.

2) Strong exception guarantee -- If the function throws an exception, the state of the program is rolled back to the state just before the function call. (for example, std::vector::push_back)


Currently point 2) is not supported by function2. The only thing that HasStrongExceptGuarantee does is to mark its move constructor, move assignment operator, and destructor as noexcept and requires the wrapped object to also have a noexcept move constructor, move assignment operator, and destructor. The reason for this is that std::vector and other containers of the standard library would default to a copy instead of a cheap move operation otherwise.

By marking the move constructor, move assignment operator, and destructor of the function2 type-erasure as noexcept we implicitly provide the strong exception guarantee through being 1) Nothrow.

I see that this case is misleading and it would be better if we rename HasStrongExceptGuarantee to HasNoexcept while changing the behavior of the original HasStrongExceptGuarantee to conforming to 2) without requiring the wrapped object to have a noexcept move constructor, move assignment operator and destructor.

jk-jeon commented 3 years ago

Thanks for clarification. Looking forward to the next release!