Skip to content

Latest commit

 

History

History
224 lines (176 loc) · 6.87 KB

view-maybe-lewgi.org

File metadata and controls

224 lines (176 loc) · 6.87 KB

View::maybe

The motivation.

In writing range transformation pipelines it is useful to be able to lift a nullable value into a view that is either empty or contains the value held by the nullable.

The adapter view::single fills a similar purpose for non-nullable values, lifting a single value into a view, and view::empty provides a range of no values of a given type.

A view::maybe adaptor also allows nullable values to be treated as ranges when it is otherwise undesirable to make them containers, for example std::optional.

Example 1

std::vector<std::optional<int>> v{
  std::optional<int>{42},
  std::optional<int>{},
  std::optional<int>{6 * 9}};

auto r = view::join(view::transform(v, view::maybe));

for (auto i : r) {
    std::cout << i; // prints 42 and 42^H^H 56
}

Example 2

std::optional o{7};
for (auto&& i : view::maybe(o)) {
    i = 9;
    std::cout << "i=" << i << " prints 9\n";
}
std::cout << "o=" << *o << " prints 9\n";

auto oe = std::optional<int>{};
for (int i : view::maybe(oe))
    std::cout << "i=" << i << '\n'; // does not print

The basics of the design.

Depending on if the underlying nullable is a temporary or not, the view either captures a copy of the nullable object in a semiregular-box or holds a pointer to the underlying object. In either case, if the nullable object is empty, data() returns nullptr, otherwise the address of the value.

The view interface function size() returns 0 or 1, and begin() and end() have natural definitions in terms of data() and size().

view::maybe

struct __maybe_fn {
    template <class T, Nullable U = std::remove_cv_t<T>>
    requires ranges::Constructible<U, T> &&
             ranges::CopyConstructible<U>
    constexpr safe_maybe_view<U> operator()(T&& t)
        const noexcept(std::is_nothrow_constructible_v<U, T>) {
        return safe_maybe_view<U>{std::move(t)}; // Safe copy
    }

    template <Nullable T>
    constexpr ref_maybe_view<T> operator()(T& t)
        const noexcept {
        return ref_maybe_view<T>{t}; // Refer to t
    }
};
inline constexpr __maybe_fn maybe{};

safe_maybe_view

Some details removed

template <Nullable Maybe>
     requires ranges::CopyConstructible<Maybe>
class safe_maybe_view
    : public ranges::view_interface<safe_maybe_view<Maybe>> {
  private:
    using T = remove_reference_t<ranges::iter_reference_t<Maybe>>;

    ranges::detail::semiregular_box<Maybe> value_;

  public:
    constexpr safe_maybe_view() = default;

    constexpr explicit safe_maybe_view(Maybe&& maybe)
        noexcept(is_nothrow_move_constructible_v<Maybe>)
        : value_(move(maybe)) {}

    constexpr T*       begin() noexcept { return data(); }
    constexpr T*       end() noexcept { return data() + size(); }

    constexpr ptrdiff_t size() const noexcept { return bool(value_.get()); }

    constexpr T* data() noexcept {
        Maybe& m = value_.get();
        return m ? addressof(*m) : nullptr;
    }
};

ref_maybe_view

Some details removed

template <Nullable Maybe>
class ref_maybe_view
    : public std::experimental::ranges::view_interface<ref_maybe_view<Maybe>> {
    using T = std::remove_reference_t<ranges::iter_reference_t<Maybe>>;

    Maybe* value_ = nullptr;

  public:
    constexpr ref_maybe_view() = default;
    constexpr explicit ref_maybe_view(Maybe& maybe) noexcept
        : value_(std::addressof(maybe)) {}

    constexpr T*       begin() noexcept { return data(); }
    constexpr T*       end() noexcept { return data() + size(); }

    constexpr std::ptrdiff_t size() const noexcept { return bool(*value_); }

    constexpr T* data() noexcept {
        return *value_ ? std::addressof(**value_) : nullptr;
    }
};

Concept Nullable

Leaving out equality preserving requirements

template <class T>
concept bool Nullable =
    std::is_object_v<T> &&
    requires(T& t, const T& ct) {
        bool(ct);
        *t;
        *ct;
    };

The history of your proposal within WG21.

Presented R0 at San Diego

“Not Small”

Encouraged to make it small.

Internally to Bloomberg got assurances that our nullable type would be made to conform with the std nullable protocol.

Important changes from previous revisions.

Removed all customization points.

Now defined purely in terms of the, non-normative, Nullable Concept.

A Nullalble is Contextually Bool and Dereferenceable.

Considered Readable, instead of dereferenceable, but that pulls in expensive traits.

Previous polls and guidance.

Discussed in San Diego: http://wiki.edg.com/bin/view/Wg21sandiego2018/P1255

encourage future work - find a solution to visiting one-or-more optionals

SFFNASA
67510

like the Maybe concept (contextually convertible to bool not by decay, dereferencable to object type)

SFFNASA
212110

prefer view::maybe vs. begin()/end() for optional<> / Maybe

SVVNOSO
67320

Your intended ship vehicle

Ideally C++20, failing that, C++23 and get it into experimental/ in range libraries.

Relevant prior art and existing best practice.

Similar facilities in wide use in functional languages, for lifting an Optional type into List.

A simplified version used in Eric Niebler’s Pythagorian Triples, Revisited

// maybe_view defines a view over zero or one
// objects.
template<Semiregular T>
struct maybe_view : view_interface<maybe_view<T>> {
  maybe_view() = default;
  maybe_view(T t) : data_(std::move(t)) {
  }
  T const *begin() const noexcept {
    return data_ ? &*data_ : nullptr;
  }
  T const *end() const noexcept {
    return data_ ? &*data_ + 1 : nullptr;
  }
private:
  optional<T> data_{};
};

Implementation and usage experience.

Implementation available at https://github.com/steve-downey/view_maybe which depends on cmcstl2.

https://github.com/steve-downey/view_maybe/blob/master/src/view_maybe/view_maybe.h

There will be a PR for CMCSTL2 soon. There was red tape involved.

This tends to be written, poorly, by many people implementing range style code.