Skip to content
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

Add a layer of operators that propagate nothingness #4

Open
gnzlbg opened this issue Sep 16, 2016 · 4 comments
Open

Add a layer of operators that propagate nothingness #4

gnzlbg opened this issue Sep 16, 2016 · 4 comments

Comments

@gnzlbg
Copy link

gnzlbg commented Sep 16, 2016

It would be nice to easily have a way to:

  • add the operators supported by the underlying type to markable (e.g. operator+ if T models RandomAccessIncrementable).
  • customize how these operators deal with the empty value (e.g. propagate emptiness, throw on emptiness, assert on emptiness in debug and UB in release, terminate on emptiness...)

I ended up adding all operators if T support them to my fork of compact optional, and asserting on emptiness by default, but different users might want different behavior here.

@gnzlbg
Copy link
Author

gnzlbg commented Sep 22, 2016

I can imagine at least three ways of doing this:

  • having the type of operator support be part of the current policy (with some ergonomics, like if the policy doesn't mention operators, users don't get any). That is, the resulting types are still markable<policy, tag>.
  • having wrapper types over markable that provides the operators: e.g. throw_on_emptiness_arithmetic_markable<policy, tag> = throw_on_emptiness_arithmetic<markable<policy, tag>>, assert_on_emptiness_arithmetic, ... The advantage of this is that these wrappers would work not only with markable but probably with std::/boost::/your::optional generically as well.
  • add an arithmetic policy to markable: markabke<policy, tag = default_tag, arithmetic_policy = default_arithmetic_policy>.

The second alternative, having a wrapper that provides arithmetic operators that propagate emptiness somehow, is the most appealing to me (mainly because these wrappers could work for all types implementing the optional interface, like std::optional, std::any, std::variant<monostate, T>, std::xxx_ptr<T>, maybe even std::future<T>).

EDIT: while maybe these operator wrappers could/should be prototyped here, and even included as part of Boost.Markable, maybe it would be worth it for them to be refactored in the future to a Boost.OptionalOperators library that ensures that they work with all "ranges of zero or one elements".

@akrzemi1
Copy link
Owner

akrzemi1 commented Oct 6, 2016

Thank you for your interest in the library. I am sorry it took so long for me to reply. If I were to repeat your suggestion with my own words, it would go like this.

My type X has member functions f1() and f2(). I want markable<X> to also have member functions f1() and f2(). Operators are just a special case of member (or non-member, but still part of the interface) functions. So, in a way, this looks like a request for a perfect wrapper. It is not doable in C++14. I could decide on a subset of all possible member functions, but any such arbitrary choice would be unfair.

@gnzlbg
Copy link
Author

gnzlbg commented Oct 6, 2016

So, in a way, this looks like a request for a perfect wrapper.

I should have worded the request better to make it clear that this was not the case (since as you said, this is not doable in C++14, nor C++17, nor will be for the time being until we get reflection or operator. in the standard).

What are your use cases for markable? I mostly use it to wrap integers, strings, and pointers. Being able to use the operators on the markable directly for any of these types makes markable infinitely nicer to use.

Currently I just maintain my own private fork of the library where I add the operators (with assert semantics) to markable itself and constrain them with concepts.

The resulting code is very nice because I don't need to deal with propagating the null value every time I use an operator. I just do that once for the type and then the propagation behavior is abstracted away.

Just compare it yourself:

// currently:
auto c = a && b? 
    a.value() + b.value() 
    : assert(false);  // replace with throw, terminate, b, ...
// vs
auto c = a + b;  // null value handling is part of the types, happens implicitly

// imagine doing with explicit null value handling:
auto d = a + b + c; 

Some times you need to do a different null value handling, or want to throw it in the face of the user, but at least in the code I write most of the time the exact type of null value handling doesn't matter.

@akrzemi1
Copy link
Owner

akrzemi1 commented Oct 6, 2016

Ok, I can see the direction you want to go. I think it is incompatible with the design criteria of this library: to make every intention of the user explicit, so that a T is never confused with a markable<T>.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants