Git Product home page Git Product logo

optional's Introduction

optional

Single header implementation of std::optional with functional-style extensions and support for references.

Documentation Status Clang + GCC: Linux Build Status MSVC: Windows Build Status

std::optional is the preferred way to represent an object which may or may not have a value. Unfortunately, chaining together many computations which may or may not produce a value can be verbose, as empty-checking code will be mixed in with the actual programming logic. This implementation provides a number of utilities to make coding with optional cleaner.

For example, instead of writing this code:

std::optional<image> get_cute_cat (const image& img) {
    auto cropped = crop_to_cat(img);
    if (!cropped) {
      return std::nullopt;
    }

    auto with_tie = add_bow_tie(*cropped);
    if (!with_tie) {
      return std::nullopt;
    }

    auto with_sparkles = make_eyes_sparkle(*with_tie);
    if (!with_sparkles) {
      return std::nullopt;
    }

    return add_rainbow(make_smaller(*with_sparkles));
}

You can do this:

tl::optional<image> get_cute_cat (const image& img) {
    return crop_to_cat(img)
           .and_then(add_bow_tie)
           .and_then(make_eyes_sparkle)
           .map(make_smaller)
           .map(add_rainbow);
}

The interface is the same as std::optional, but the following member functions are also defined. Explicit types are for clarity.

  • map: carries out some operation on the stored object if there is one.
    • tl::optional<std::size_t> s = opt_string.map(&std::string::size);
  • and_then: like map, but for operations which return a tl::optional.
    • tl::optional<int> stoi (const std::string& s);
    • tl::optional<int> i = opt_string.and_then(stoi);
  • or_else: calls some function if there is no value stored.
    • opt.or_else([] { throw std::runtime_error{"oh no"}; });
  • map_or: carries out a map if there is a value, otherwise returns a default value.
    • tl::optional<std::size_t> s = opt_string.map_or(&std::string::size, 0);
  • map_or_else: carries out a map if there is a value, otherwise returns the result of a given default function.
    • std::size_t get_default();
    • tl::optional<std::size_t> s = opt_string.map_or_else(&std::string::size, get_default);
  • conjunction: returns the argument if a value is stored in the optional, otherwise an empty optional.
    • tl::make_optional(42).conjunction(13); //13
    • tl::optional<int>{}.conjunction(13); //empty
  • disjunction: returns the argument if the optional is empty, otherwise the current value.
    • tl::make_optional(42).disjunction(13); //42
    • tl::optional<int>{}.disjunction(13); //13
  • take: returns the current value, leaving the optional empty.
    • opt_string.take().map(&std::string::size); //opt_string now empty;

In addition to those member functions, optional references are also supported:

int i = 42;
tl::optional<int&> o = i;
*o == 42; //true
i = 12;
*o == 12; //true
&*o == &i; //true

Assignment has rebind semantics rather than assign-through semantics:

int j = 8;
o = j;

&*o == &j; //true

Compiler support

Tested on:

  • Linux
    • clang 6.0.1
    • clang 5.0.2
    • clang 4.0.1
    • clang 3.9
    • clang 3.8
    • clang 3.7
    • clang 3.6
    • clang 3.5
    • g++ 8.0.1
    • g++ 7.3
    • g++ 6.4
    • g++ 5.5
    • g++ 4.9
    • g++ 4.8
  • Windows
    • MSVC 2015
    • MSVC 2017

Standards Proposal

This library also serves as an implementation of WG21 standards paper P0798R0: Monadic operations for std::optional. This paper proposes adding map, and_then, and or_else to std::optional.


CC0

To the extent possible under law, Sy Brand has waived all copyright and related or neighboring rights to the optional library. This work is published from: United Kingdom.

optional's People

Contributors

basiliscos avatar bruxisma avatar decoyrs avatar denizevrenci avatar glynos avatar idragnev avatar johvik avatar kbenzie avatar mortenfyhn avatar rollbear avatar tartanllama avatar thephd avatar tindarid avatar yuri12358 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

optional's Issues

Plan for new release?

Hi @TartanLlama , thanks a lot for the tl-optional library.

Do you think it could make sense to have a new tag for this optional library? I was planning to package optional in conda-forge, and I was interested in several bug fixes that are on master but that are not part of the last tag. While I could incorporate them as additional patches on top of the v1.0.0, if it is feasible to tag a new version (and bump https://github.com/TartanLlama/optional/blob/master/CMakeLists.txt#L5), it would great to use directly that.

Let me know if I can help in any way in creating a new tag, thanks!

CMake generation fails for Release build with tests on and documentation off

I'm building the package on Devuan ASCII (= Debian Stretch without systemd), with CMake 3.13.1 under /usr/local/src, locally built from a source tarball.

I've done the following:

  • clone the repo to /path/to/tl-optional
  • cd /path/to/tl-optional
  • ccmake .
  • Set build type to Release (I realize that shouldn't matter much)
  • Set documentation to OFF
  • Configure again
  • Generate

and I get:

 CMake Error at CMakeLists.txt:34 (add_executable):
   Cannot find source file:

     /path/to/tl-optional/tests/main.cpp

   Tried extensions .c .C .c++ .cc .cpp .cxx .cu .m .M .mm .h .hh .h++ .hm
   .hpp .hxx .in .txx


 CMake Error: Cannot determine link language for target "tests".
 CMake Error: CMake can not determine linker language for target: tests

Current master is broken (cmake error)

Even when running Travis-CI/AppVeyor on the current master branch, they fail with the following error:

$ mkdir build && cd build && cmake -DCXXSTD=$CXXSTD .. && make && ./tests
-- The CXX compiler identification is GNU 5.5.0
-- Check for working CXX compiler: /usr/bin/g++-5
-- Check for working CXX compiler: /usr/bin/g++-5 -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
CMake Error at build/_deps/tl_cmake-src/add-tl.cmake:6 (target_sources):
  target_sources may only set INTERFACE properties on INTERFACE targets
Call Stack (most recent call first):
  CMakeLists.txt:19 (tl_add_library)
-- Configuring incomplete, errors occurred!

Since the same revision passed CI 5 days ago, I suspect it has to do with a change in the tl-cmake repository 4 days ago. I have no idea how this CMake stuff works exactly, but in the CMakeLists.txt I see a dependency to https://github.com/TartanLlama/tl-cmake.git without specifying an exact revision. This is generally a very bad idea since it's impossible to get stable, reproducible builds.

So I would suggest to link to a specific revision (tag or commit SHA). Or even better, remove the dependency completely ๐Ÿ˜‰

#31 is currently blocked by this issue since CI always fails.

tl::optional cannot be used with exceptions disabled

It's not possible to use tl::optional with exceptions disabled. It would be useful for code that is required to compile without exception support if this were possible. With libstdc++, abort is called instead for conditions that would raise an exception.

It unfortunately varies across compilers how to detect if exception support has been disabled, but in a third party variant implementation this is done by https://github.com/mpark/variant/blob/d1cdfdd3f2ed80710ba4d671fe6bffaa3e28201a/v1.4.0/variant.hpp#L270 with __has_feature defined for non-clang compilers as https://github.com/mpark/variant/blob/d1cdfdd3f2ed80710ba4d671fe6bffaa3e28201a/v1.4.0/variant.hpp#L232.

`tl::optional` comparison operators do not work with template checks

Checking with templates whether tl::optional<T> is comparable returns the wrong result, if T is not comparable (check works with std::optional). For example:

#include <experimental/type_traits>

template <typename T, typename U>
using equality_comparison = decltype(std::declval<T const&>() == std::declval<U const&>());

struct NoCompare {}; // lacks comparison operators

// this assert fails, but equal comparison with tl::optional<NoCompare> doesn't compile
static_assert(!std::experimental::is_detected<equality_comparison, tl::optional<NoCompare>, NoCompare>::value,
              "'tl::optional<NoCompare> == NoCompare' should not compile");

Note: I ran into this issue using tl::optional with Trompeloil (https://github.com/rollbear/trompeloeil), as Trompeloil checks this way whether function parameters are comparable to nullptr.

Interaction with containers

Let's consider the following example:

struct ipv4;
optional<ipv4> parse_ipv4(string_view);
// Useful for parsing some config for example
optional<vector<ipv4>> parse_many_ips(vector<string_view> const&) {
  // how do I implement this?
}

With std::optional such function could be implemented as follows:

std::optional<vector<ipv4>> parse_many_ips(vector<string_view> const& strs) {
  vector<ipv4> result;
  for (auto s : strs) {
    if (auto ip = parse_ipv4(s)) {
      result.emplace_back(*ip);
    } else {
      return std::nullopt;
    }
  }
  return result;
}

And with tl::optional... The same? The additional functions don't seem to provide any help. On the other hand, in a functional language this would be a one-liner: parseManyIps = traverse parseIpv4.

Providing full standard library support for Foldable/Traversable concepts does obviously seem out of scope for this library, however ad-hoc support for some common cases such as the example above would be quite useful.

Return type not correctly deduced

Consider this code:

tl::optional<int> op = 42;
    op.map([](auto i) {
        if (i == 42)
        {
            return tl::make_optional("good");
        }
        return tl::nullopt;
    });

This won't compile.
The error message is:
auto' in return type deduced as 'utils::nullopt_t' here but deduced as 'utils::optional<const char *>' in earlier return statement
Should't tl::nullopt be a value of type tl::optional<> for all contained types?

or_else of an Optional with a reference

All implementations of or_else in the case of an optional with a reference return optional<T>.
I ran into an issue with that, and when changing all signatures to optional<T&> it was fixed.
Isn't returning an optional<T> in these cases creating a copy of the contained reference?

Example:

struct IF
{
	virtual void foo() = 0;
};

struct Impl : IF
{
	void foo() override {}
};
Impl instance{};

tl::optional<IF&> test = tl::optional<IF&>{instance}.or_else([]() { std::cout << "bad"; });

`tl::optional<T&>` should be constructible from a `tl::optional<T>`.

To me the biggest use of optional<T&> is in arguments like void foo(optional<const Heavy&>) where the naive void foo1(const optional<Heavy>&) of course means foo1(heavy) has to copy the heavy to a optional<Heavy>. Of course, if I have an optional<Heavy> optHeavy I can call foo1(optHeavy), but if I call foo(optHeavy), tl::optional won't implicitly convert from const optional<Heavy> to optional<const Heavy&>.

That is, I'd like optional<T&> to be constructible from optional<T>. That would let foo(optHeavy) Just Work. (Or maybe just let optional<const T&> construct from const optional<T>&?)

Suppress MSVC warnings 4582 and 4583

MSVC (v142) issues the warning 4582 ("constructor is not implicitly called") on tl::detail::optional_storage_base<T,true> , like so:

optional\include\tl/optional.hpp(385,1): warning C4582: 'tl::detail::optional_storage_base<T,true>::m_value': constructor is not implicitly called [...]
          with [ T=std::pair<int,double> ]
optional\include\tl/optional.hpp(384): message : while compiling class template member function 'tl::detail::optional_storage_base<T,true>::optional_storage_base(void) noexcept' [...]
          with [ T=std::pair<int,double> ]

The compiler is right: the constructor of the m_value field of the m_dummy union is not called. But this is intentional, so the warning is not useful and could be suppressed.

Similarly, the warning 4583 (destructor not explicitly called) is issued on tl::detail::optional_storage_base<T,false>:

optional\include\tl/optional.hpp(366,1): warning C4583: 'tl::detail::optional_storage_base<T,false>::m_value': destructor is not implicitly called [...]
          with [ T=`anonymous-namespace'::TestClass ]
optional\include\tl/optional.hpp(366): message : while compiling class template member function 'tl::detail::optional_storage_base<T,false>::~optional_storage_base(void)' [...]
          with [ T=`anonymous-namespace'::TestClass ]

One solution to suppress this is to wrap

#pragma warning( push )
#pragma warning( disable: 4582 4583 )
[...]
#pragma warning( pop )

around the code. I'll open a corresponding merge request for your consideration.

CTAD on MSVC fails

Hi, first of all thanks for this library!

On MSVC 19.21 with C++17, CTAD fails with tl::optional whereas it works with std::optional:

tl::optional o = 3; // Cannot deduce T
std::optional o = 3; // Deduces T to be int

I think this is just a case of missing std::optional's deduction guide, since adding that in fixes it, but strangely Clang and GCC don't seem to need the guide and deduce T to be int anyway. I don't know enough about CTAD to know exactly what's going on here, so sorry if this isn't actually a bug in tl::optional and is instead a compiler bug.

Compile error with Clang 3.4

Hi, thanks for this nice library!

Unfortunately it doesn't compile with Clang 3.4 (which is shipped with Ubuntu Trusty, and thus also installed on Travis-CI). I see in your readme that you tested with Clang >= 3.5. Would it be hard to add support for Clang 3.4?

Following compile errors occur:

$ clang++ -std=c++11 optional.hpp

../libs/optional/tl/optional.hpp:388:27: error: no member named 'is_trivially_copy_constructible' in namespace 'std'
template <class T, bool = TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T)>
                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../libs/optional/tl/optional.hpp:60:8: note: expanded from macro 'TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE'
  std::is_trivially_copy_constructible<T>::value
  ~~~~~^
../libs/optional/tl/optional.hpp:388:71: error: 'T' does not refer to a value
template <class T, bool = TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T)>
                                                                      ^
../libs/optional/tl/optional.hpp:60:40: note: expanded from macro 'TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE'
  std::is_trivially_copy_constructible<T>::value
                                       ^
../libs/optional/tl/optional.hpp:388:17: note: declared here
template <class T, bool = TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T)>
                ^
../libs/optional/tl/optional.hpp:388:27: error: no type named 'value' in the global namespace
template <class T, bool = TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T)>
                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../libs/optional/tl/optional.hpp:60:44: note: expanded from macro 'TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE'
  std::is_trivially_copy_constructible<T>::value
                                         ~~^
../libs/optional/tl/optional.hpp:388:73: error: expected unqualified-id
template <class T, bool = TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T)>
                                                                        ^
../libs/optional/tl/optional.hpp:395:8: error: no template named 'optional_copy_base'; did you mean 'optional_storage_base'?
struct optional_copy_base<T, false> : optional_operations_base<T> {
       ^~~~~~~~~~~~~~~~~~
       optional_storage_base
../libs/optional/tl/optional.hpp:302:8: note: 'optional_storage_base' declared here
struct optional_storage_base {
       ^
../libs/optional/tl/optional.hpp:398:3: error: C++ requires a type specifier for all declarations
  optional_copy_base() = default;
  ^~~~~~~~~~~~~~~~~~
../libs/optional/tl/optional.hpp:399:28: error: unknown type name 'optional_copy_base'; did you mean 'optional_storage_base'?
  optional_copy_base(const optional_copy_base &rhs) {
                           ^~~~~~~~~~~~~~~~~~
                           optional_storage_base
../libs/optional/tl/optional.hpp:395:8: note: 'optional_storage_base' declared here
struct optional_copy_base<T, false> : optional_operations_base<T> {
       ^
../libs/optional/tl/optional.hpp:399:3: error: C++ requires a type specifier for all declarations
  optional_copy_base(const optional_copy_base &rhs) {
  ^~~~~~~~~~~~~~~~~~
../libs/optional/tl/optional.hpp:407:22: error: unknown type name 'optional_copy_base'; did you mean 'optional_storage_base'?
  optional_copy_base(optional_copy_base &&rhs) = default;
                     ^~~~~~~~~~~~~~~~~~
                     optional_storage_base
../libs/optional/tl/optional.hpp:395:8: note: 'optional_storage_base' declared here
struct optional_copy_base<T, false> : optional_operations_base<T> {
       ^
../libs/optional/tl/optional.hpp:407:3: error: C++ requires a type specifier for all declarations
  optional_copy_base(optional_copy_base &&rhs) = default;
  ^~~~~~~~~~~~~~~~~~
../libs/optional/tl/optional.hpp:408:3: error: unknown type name 'optional_copy_base'; did you mean 'optional_storage_base'?
  optional_copy_base &operator=(const optional_copy_base &rhs) = default;
  ^~~~~~~~~~~~~~~~~~
  optional_storage_base
../libs/optional/tl/optional.hpp:395:8: note: 'optional_storage_base' declared here
struct optional_copy_base<T, false> : optional_operations_base<T> {
       ^
../libs/optional/tl/optional.hpp:408:39: error: unknown type name 'optional_copy_base'; did you mean 'optional_storage_base'?
  optional_copy_base &operator=(const optional_copy_base &rhs) = default;
                                      ^~~~~~~~~~~~~~~~~~
                                      optional_storage_base
../libs/optional/tl/optional.hpp:395:8: note: 'optional_storage_base' declared here
struct optional_copy_base<T, false> : optional_operations_base<T> {
       ^
../libs/optional/tl/optional.hpp:409:3: error: unknown type name 'optional_copy_base'; did you mean 'optional_storage_base'?
  optional_copy_base &operator=(optional_copy_base &&rhs) = default;
  ^~~~~~~~~~~~~~~~~~
  optional_storage_base
../libs/optional/tl/optional.hpp:395:8: note: 'optional_storage_base' declared here
struct optional_copy_base<T, false> : optional_operations_base<T> {
       ^
../libs/optional/tl/optional.hpp:409:33: error: unknown type name 'optional_copy_base'; did you mean 'optional_storage_base'?
  optional_copy_base &operator=(optional_copy_base &&rhs) = default;
                                ^~~~~~~~~~~~~~~~~~
                                optional_storage_base
../libs/optional/tl/optional.hpp:395:8: note: 'optional_storage_base' declared here
struct optional_copy_base<T, false> : optional_operations_base<T> {
       ^
../libs/optional/tl/optional.hpp:418:32: error: no template named 'is_trivially_move_constructible' in namespace 'std'; did you mean 'is_trivially_destructible'?
template <class T, bool = std::is_trivially_move_constructible<T>::value>
                          ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                               is_trivially_destructible
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/type_traits:1204:12: note: 'is_trivially_destructible' declared here
    struct is_trivially_destructible
           ^
../libs/optional/tl/optional.hpp:419:29: error: no template named 'optional_copy_base'; did you mean 'optional_move_base'?
struct optional_move_base : optional_copy_base<T> {
                            ^~~~~~~~~~~~~~~~~~
                            optional_move_base
../libs/optional/tl/optional.hpp:419:8: note: 'optional_move_base' declared here
struct optional_move_base : optional_copy_base<T> {
       ^
../libs/optional/tl/optional.hpp:420:9: error: no template named 'optional_copy_base'; did you mean 'optional_move_base'?
  using optional_copy_base<T>::optional_copy_base;
        ^~~~~~~~~~~~~~~~~~
        optional_move_base
../libs/optional/tl/optional.hpp:419:8: note: 'optional_move_base' declared here
struct optional_move_base : optional_copy_base<T> {
       ^
../libs/optional/tl/optional.hpp:425:58: error: no template named 'optional_copy_base'; did you mean 'optional_move_base'?
template <class T> struct optional_move_base<T, false> : optional_copy_base<T> {
                                                         ^~~~~~~~~~~~~~~~~~
                                                         optional_move_base
../libs/optional/tl/optional.hpp:419:8: note: 'optional_move_base' declared here
struct optional_move_base : optional_copy_base<T> {
       ^
../libs/optional/tl/optional.hpp:426:9: error: no template named 'optional_copy_base'; did you mean 'optional_move_base'?
  using optional_copy_base<T>::optional_copy_base;
        ^~~~~~~~~~~~~~~~~~
        optional_move_base
../libs/optional/tl/optional.hpp:419:8: note: 'optional_move_base' declared here
struct optional_move_base : optional_copy_base<T> {
       ^
fatal error: too many errors emitted, stopping now [-ferror-limit=]
20 errors generated.

Disable documentation building by default to reduce dependencies

When documentation is enabled, this package has external dependencies, which is IMHO undesirable and somewhat surprising to cloners. I suggest, therefore, the following change in CMakeLists.txt.

-option(OPTIONAL_ENABLE_DOCS "Enable documentation." ON)
+option(OPTIONAL_ENABLE_DOCS "Enable documentation." OFF)

Please compare to C++17's declaration-in-if in the readme

For the readme's "For example, instead of writing this code:", the example could also be written using C++17's declaration-in-if syntax:

std::optional<image> get_cute_cat (const image& img) {
    if (auto cropped = crop_to_cat(img)) {
        if (auto with_tie = add_bow_tie(*cropped)) {
            if (auto with_sparkles = make_eyes_sparkle(*with_tie)) {
                return add_rainbow(make_smaller(*with_sparkles));
            }
        }
    }

    return std::nullopt;
}

I'm looking at the library because it's been mentioned on a Debian mailing list, but AFAICS the syntax built into C++17 already provides these benefits.

Conflicting implementations of map_imp in optional.hpp and expected.hpp

Hey,

I am using both your implementation for optional and expected in the same project,
and I got compile issues when including both in the same file and using map on either of them:

// Minimal code to expose this:
#include "expected.hpp"
#include "optional.hpp"

void MyFunction()
{
    tl::optional<int> my_optional;
    my_optional.map([](int value) { return value + 1; });
}

Compilation failure:

optional.h: In instantiation of 'constexpr auto tl::optional<T>::map(F&&) & [with F = MyFunction()::<lambda(int)>; T = int]':
file.cc:14:56:   required from here
optional.h:770:20: error: call of overloaded 'map_impl(tl::optional<int>&, MyFunction()::<lambda(int)>)' is ambiguous
     return map_impl(*this, std::forward<F>(f));
            ~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~

The problem is that both optional and expected use a helper called map_impl, but with different implementations.

A simple solution I applied is simply renaming map_impl to optional_map_impl and expected_map_impl, respectively.

Jeroen

PS Thanks a lot for these libraries, I love them :)

value_or(...) && problem with move-only types

return has_value() ? **this : static_cast<T>(std::forward<U>(u));

Usage of **this leads to a compile-time error when T is move-only, because *this is an lvalue ref even in &&-qualified member function. Because of this, instead of operator*() && version operator*() & is being called, and that leads to an attempt to copy the inner T. This fact is demonstrated here: godbolt.

The fix is pretty simple (use std::move(*this)), I'm going to prepare a PR.

Self-assignment not handled properly

Hi,

It seems that the assignment operator of optional<T> does not handle self-assignment properly, which can cause fatal errors (my application crashes because of that). IMHO self-assignment must always be safe and should actually do nothing.

I checked it with following test:

struct fail_on_copy_self {
    int value;
    fail_on_copy_self(int v) : value(v) {}
    fail_on_copy_self(const fail_on_copy_self& other) {
        REQUIRE(&other != this);
    }
};

TEST_CASE("Assignment to self", "[assignment.self]") {
    tl::optional<fail_on_copy_self> o = fail_on_copy_self(42);

    o = o;
    REQUIRE(o->value == 42);
}

Output:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tests is a Catch v2.0.1 host application.
Run with -? for options

-------------------------------------------------------------------------------
Assignment to self
-------------------------------------------------------------------------------
assignment.cpp:81
...............................................................................

assignment.cpp:77: FAILED:
  REQUIRE( &other != this )
with expansion:
  0x00007fff63b977f0 != 0x00007fff63b977f0

terminate called after throwing an instance of 'Catch::TestFailureException'
assignment.cpp:81: FAILED:
  {Unknown expression after the reported line}
due to a fatal error condition:
  SIGABRT - Abort (abnormal termination) signal

===============================================================================
test cases:  12 |  11 passed | 1 failed
assertions: 326 | 324 passed | 2 failed

Abgebrochen

As you can see, the copy constructor of type T is called with &other == this, which seems very strange. That's not intended, is it? ;)

Suggestion: `take()` should always move

Hello, thank you for this library! It does make optional much more pleasant to use.

Since take() will always empty the optional it is invoked upon, I expected it to always move. However, I noticed that there are const and non-const / lvalue and rvalue overloads for this function, and only the non-const-rvalue one will move. Is there a particular reason for this? The const overloads are not usable anyway, as reset() is non-const.

I suggest replacing all overloads with:

  optional take() {
    optional ret = std::move(*this);
    reset();
    return ret;
  }

A moving take() can be helpful when using optionals of move-only types, for example

tl::optional<some_move_only_t> opt = ...;
// ...
some_move_only_t value = opt.take().value();

is a bit neater to me than:

tl::optional<some_move_only_t> opt = ...;
// ...
some_move_only_t value = std::move(opt).value();

Especially since the latter leaves the optional non-empty, but with a moved-from value.
Right now, to get the same effect, you would have to do like so:

tl::optional<some_move_only_t> opt = ...;
// ...
some_move_only_t value = std::move(opt).take().value();

Which is a bit noisy.

Also, perhaps there is some merit to having a take_value() member function that is semantically equivalent to take().value() but avoids constructing a temporary optional -- what do you think?

Add a trivial `apply` function

Imagine a scenario where a several transformations have to be applied to an optional:

optional<Tb> func_a(Ta);
optional<Tc> func_b(optional<Tb>);
optional<Td> func_c(Tc);

optional<Ta> opt;

Currently this is done in this way, where orders in which functions are written and applied is inconsistent:

func_b(opt.and_then(func_a)).and_then(func_c);

I propose adding a trivial member function apply(F f) to optional that applies f to *this, so the code above can be written as:

opt.and_then(func_a).apply(func_b).and_then(func_c);

In this example, function names are written down in the order they are called, so it looks much cleaner. Probably if we could have UFCS, this wouldn't be needed.

In real world these functions could be lambdas written in-line and consisting of several lines, so this order inconsistency may look really ugly.

memory leaks and 'use after move' bugs inside assignment operators implementation

Hi,

The bugs could be triggered with the following code:

#include <tl/optional.hpp>
#include <vector>
#include <iostream>

struct VectorOfIntWrapper
{                              
    VectorOfIntWrapper(std::vector<int>&& other) : data(std::move(other)) {}
    void operator=(std::vector<int>&& other){ data = std::move(other); }
    std::vector<int> data;
};

int main()
{
    tl::optional<VectorOfIntWrapper> fst = VectorOfIntWrapper{{1, 2, 3, 4}};
    tl::optional<std::vector<int>> sec = std::vector<int>{5, 6, 7, 8};      
    fst = std::move(sec);  // memory leak and invalid result
    std::cout << fst->data.size(); // prints 0
}

Live demo: https://godbolt.org/z/EsYWKP3Ge

Issues could be fixed by not calling this->construct(...) in case the value is already present(by adding 'else' in lines 1186 and 1208):

  optional &operator=(optional<U> &&rhs) {
    if (has_value()) {
      if (rhs.has_value()) {
        this->m_value = std::move(*rhs);
      } else {
        this->hard_reset();
      }
    }
    else if (rhs.has_value()) {
      this->construct(std::move(*rhs));
    }

    return *this;
  }

Feature suggestion: Free monadic-optional functions usable with pipe syntax.

Feel free to close if you don't like this idea, but in our C++17 (soon-to-be-20) codebase, we've added namespace MonadicOptional that has pipeable transform, and_then, etc. These are cool in that (a) they let us operate on std::optional pre-C++23, but also, they let us treat pointers the same way:

using namespace MonadicOptional;
STATIC_REQUIRE(
    (std::make_unique<int>(80) //
     | and_then([](auto x) { return std::optional{x / 4}; })
     | transform([](auto x) { return x + 1; })
     | and_then([](auto x) { return std::optional{x * 2}; }))
    == std::optional{42}
);

This feels useful even after we get C++23. Would you be interested in including functionality like this in this library?

nvcc and CUDA support

Has this optional class implementation been tested with CUDA? That is, compiled and tested with nvcc either for CPU-side code or adapted for GPU-device-side use and then compiled-and-tested?

If it has, it would be useful to have this mentioned in the documentation, and if GPU-device-side operation is also supported - have the methods decorated with host device when they're not constexpr (only when compiling with NVCC of course, i.e. a decoration macro which expands to nothing when not compiling with NVCC).

add value_or_cast<U>

add support for value_or_cast

this is useful when using unsigned and signed types

tl::optional<size_t> x = get_PossibleVal();

example 1

// equivalent to:    float a = x.hasValue() ? *x : static_cast<size_t>(NAN);
// ERROR: size_t is unsigned type and will convert to size_t 0
// ERROR: resulting float is [value or 0] instead of [value or NAN]
float a = x.value_or<float>(NAN);

// equivalent to:    float b = x.hasValue() ? static_cast<float>(*x) : NAN;
// resulting float is value or NAN
float b = x.value_or_cast<float>(NAN);

example 2

// equivalent to:    float a = x.hasValue() ? *x : static_cast<size_t>(-1);
// ERROR: size_t is unsigned type and will convert to size_t 18446744073709551615
// ERROR: resulting float is [value or 1.8446744073709551615] instead of [value or -1]
float a = x.value_or<float>(-1);

// equivalent to:    float b = x.hasValue() ? static_cast<float>(*x) : -1;
// resulting float is value or -1
float b = x.value_or_cast<float>(-1);

new code

        /// Returns the stored value if there is one, otherwise returns `u`
        template <class U> constexpr U value_or_cast(U &&u) const & {
            static_assert(std::is_copy_constructible<U>::value &&
                          std::is_convertible<T, U>::value,
                          "U must be copy constructible and convertible from T");
            return has_value() ? static_cast<U>(**this) : std::forward<U>(u);
        }

        /// Returns the stored value if there is one, otherwise returns `u`
        template <class U> TL_OPTIONAL_11_CONSTEXPR U value_or_cast(U &&u) && {
            static_assert(std::is_move_constructible<U>::value &&
                          std::is_convertible<T, U>::value,
                          "U must be move constructible and convertible from T");
            return has_value() ? static_cast<U>(**this) : std::forward<U>(u);
        }
        /// Returns the stored value if there is one, otherwise returns `u`
        template <class U> constexpr U value_or_cast(U &&u) const & noexcept {
            static_assert(std::is_copy_constructible<U>::value &&
                          std::is_convertible<T, U>::value,
                          "U must be copy constructible and convertible from T");
            return has_value() ? static_cast<U>(**this) : std::forward<U>(u);
        }

        /// \group value_or_cast
        template <class U> TL_OPTIONAL_11_CONSTEXPR U value_or_cast(U &&u) && noexcept {
            static_assert(std::is_move_constructible<U>::value &&
                          std::is_convertible<T, U>::value,
                          "U must be move constructible and convertible from T");
            return has_value() ? static_cast<U>(**this) : std::forward<U>(u);
        }

Removing direct value access

Hi,
I wonder what would be the fastest way on removing the error-prone * and -> operators for direct value access?
Towards a more monadic implementation I do not want the users accessing these public and unsafe operators.
Would it make sense to declare all the non-class templates as friends to the optional class?
I'm not an expert in library design and your implementation is way beyond my abilities...

Thx
Juergen

add a value_or_eval similar to boost::optional

Hi TL
Thanks for your great library.

Was wondering if you had considered a value_or_eval, similar to the existing value_or, which returns the current value if it exists, or returns value from a lambda etc, but only executes if the optional is empty.

The point is to enable lazy evaluation, especially for expensive calculations.

(This exists in boost::optional, but not sure if there was a reason not to include it in the std.)

Thanks.

Here is a implementation from boost 1.61 for example

    template <typename F>
    value_type value_or_eval ( F f ) const&
      {
        if (this->is_initialized())
          return get();
        else
          return f();
      }
      
    template <typename F>
    value_type value_or_eval ( F f ) &&
      {
        if (this->is_initialized())
          return boost::move(get());
        else
          return f();
      }

No way to contact the author

Simon, you've not listed an email (or real-world) address at which you can be contacted. You've listed what seems to be a Twitter handle, but Twitter is a proprietary platform which cannot serve as a substitute for a proper address. Can you please add one to the sources and/or to your GitHub profile?

std::hash<tl::optional<T>> sfinae

Hi!
I saw within the code that the hash specialization of tl::optional still needed sfinae protection.
I recently added a pull request that addresses this problem!
:)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.