patterncppMinor
An attempt at implementing Maybe in C++11
Viewed 0 times
maybeimplementingattempt
Problem
I gave a shot at implementing
This is the first time I'm using
Maybe.hpp:
```
#include
#include
#include
namespace ro {
/*
* A class that may or may not hold a value in it.
*/
template
class Maybe
{
public:
/*
* A deleter that doesn't actually delete the pointer. This is used to make sure that
* the thread_local static instance on the stack doesn't get deleted when going out
* of scope
*/
struct NoopDeleter
{
void operator()(Maybe*) {}
};
using pointer_t = std::shared_ptr>;
/*
* Gets an pointer to a Maybe that's nothing
*/
static pointer_t nothing();
/*
* Gets a pointer to a Maybe that just have a value.
*/
static pointer_t just(const T& value);
public:
Maybe() = default;
virtual ~Maybe() = default;
/*
* Returns if this Maybe is nothing
*/
virtual bool isNothing() const = 0;
/*
* Gets the value, if this instance has one. Throws a runtimer_error otherwise.
*/
virtual T get() const = 0;
/*
* Gets the value held or the passed in value otherwise.
*/
T getOrElse(const T& defaultValue) const
{
if (isNothing())
{
return defaultValue;
}
return get();
}
/*
* Gets the value stored or throws the exception as supplied by the method passed in
*/
T getOrThrow(const std::function& exceptionSupplier) const
{
if (isNothing())
{
throw exceptionSupplier
Maybe for C++ and a slight twist in my implementation is that it uses thread_local static instance of Just and Nothing + placement new operator to minimize the number of (de)allocations.This is the first time I'm using
thread_local and placement new operator, so I could be doing something really wrong here. I would appreciate if you could take a look at the code and give your feedback.Maybe.hpp:
```
#include
#include
#include
namespace ro {
/*
* A class that may or may not hold a value in it.
*/
template
class Maybe
{
public:
/*
* A deleter that doesn't actually delete the pointer. This is used to make sure that
* the thread_local static instance on the stack doesn't get deleted when going out
* of scope
*/
struct NoopDeleter
{
void operator()(Maybe*) {}
};
using pointer_t = std::shared_ptr>;
/*
* Gets an pointer to a Maybe that's nothing
*/
static pointer_t nothing();
/*
* Gets a pointer to a Maybe that just have a value.
*/
static pointer_t just(const T& value);
public:
Maybe() = default;
virtual ~Maybe() = default;
/*
* Returns if this Maybe is nothing
*/
virtual bool isNothing() const = 0;
/*
* Gets the value, if this instance has one. Throws a runtimer_error otherwise.
*/
virtual T get() const = 0;
/*
* Gets the value held or the passed in value otherwise.
*/
T getOrElse(const T& defaultValue) const
{
if (isNothing())
{
return defaultValue;
}
return get();
}
/*
* Gets the value stored or throws the exception as supplied by the method passed in
*/
T getOrThrow(const std::function& exceptionSupplier) const
{
if (isNothing())
{
throw exceptionSupplier
Solution
Allocation
The thing that jumps out at me most is the need for memory allocation to create a
It additionally makes the usage of the class a bit awkward. What I want to do:
but I have to write:
That's awkward.
You have
Returning
Throughout, you use
At the most basic, take
If you want to add more SFINAE goodness here, you could additionally require that
But this is just an overhead thing. Your implementations of
because I have to write it like:
Prefer something like:
This would let users call your functions with raw lambdas - which is what they'd expect to be able to use!
The thing that jumps out at me most is the need for memory allocation to create a
Just or Nothing. That's a performance hit, which is why boost::optional and what will eventually be std::optional don't do it this way. It additionally makes the usage of the class a bit awkward. What I want to do:
Maybe result = foo(x);
if (isNothing(result)) { ... } // or any other way to checkbut I have to write:
Maybe::pointer_t result = foo(x);
if (result->isNothing()) { ... }That's awkward.
get()You have
get() returning by value, this incurs unnecessary copies at best, but if T isn't copyable makes Just useless. You should instead prefer:virtual T& get() = 0;
virtual T const& get() const = 0;Returning
T for getOrElse() makes sense though - as you may want to support the else case as a temporary. On the other hand, getOrThrow() will only ever return get() so it should return a reference. std::function is for type erasureThroughout, you use
std::function. But std::function is for type erasure. It's for those cases where you need to store a functor. In none of your usages do you need this feature.At the most basic, take
getOrThrow(). Just take it as template argument:template
T& getOrThrow(F&& exceptionSupplier) {
if (isNothing()) {
throw std::forward(exceptionSupplier)();
}
return get();
}
template
T const& getOrThrow(F&& exceptionSupplier) const {
return const_cast(this)->getOrThrow(std::forward(exceptionSupplier));
}If you want to add more SFINAE goodness here, you could additionally require that
exceptionSupplier() gives you something that inherits from std::exception. But this is just an overhead thing. Your implementations of
map, flatMap, bind, and flatBind all take as arguments a std::function of some sort too. This is ok but inefficient for bind and flatBind, but makes map and flatMap much less usable:Maybe::pointer_t result = ...;
auto add1 = result->flatMap([](int i){ return just(i+1); }); // errorbecause I have to write it like:
auto add1 = result->flatMap([](int i){ return just(i+1); }); // OK but blargh!Prefer something like:
template ()(std::declval()), // this is your Maybe::pointer_t
typename MaybeU = typename R::element_type // this is your Maybe
>
R flatMap(F&& func)
{
return isNothing()
? MaybeU::nothing()
: std::forward(func)(get());
}This would let users call your functions with raw lambdas - which is what they'd expect to be able to use!
Code Snippets
Maybe<int> result = foo(x);
if (isNothing(result)) { ... } // or any other way to checkMaybe<int>::pointer_t result = foo(x);
if (result->isNothing()) { ... }virtual T& get() = 0;
virtual T const& get() const = 0;template <typename F>
T& getOrThrow(F&& exceptionSupplier) {
if (isNothing()) {
throw std::forward<F>(exceptionSupplier)();
}
return get();
}
template <typename F>
T const& getOrThrow(F&& exceptionSupplier) const {
return const_cast<Maybe*>(this)->getOrThrow(std::forward<F>(exceptionSupplier));
}Maybe<int>::pointer_t result = ...;
auto add1 = result->flatMap([](int i){ return just(i+1); }); // errorContext
StackExchange Code Review Q#101769, answer score: 5
Revisions (0)
No revisions yet.