0

For my purposes, I need to be able to say that my access to the .value of an optional is guaranteed to be valid. In the past, I have used a macro that checked against the .has_value() and called return T to jump out of execution, and then instantiated a local reference to the return of .value, as that meant that I never directly called .value myself, and only after a macro did the checked work. However, the same safety rules I am working under which do not allow me to call .value directly, also limit me to not use macros as they are also deemed unsafe.

Is there any way to conditionally either construct an object on the stack or return in the C++ language?

I have considered using .and_then(...), but in the case of two optionals needed at once, the code swiftly becomes unreadable (which is another safety metric I must pass).

EDIT: other limitations:

  • I have to use C++14 at the latest.
  • I cannot use exceptions.
  • No heap usage.
  • No "hiding" the access to value or has_value.
  • Removing the usage of optionals is possible, but I am looking for options to not remove them as the effort involved in changing a codebase which uses them liberally to one which does not is a lot of work. As far as I can see from comments and answers so far, it looks like there is no way to keep the way we code "Rust-like".
  • The Y of the XY problem is: We want to be able to claim there are no terminates in any of the reachable code branches.
  • The X is this, we want to not have to worry about the terminate which does exist in the .value call by claiming our code never actually calls it without first verifying by using .has_value
Richard Fabian
  • 697
  • 1
  • 5
  • 18
  • 2
    This reads like an XY-problem. Why are you using std::optional, if the local rules make using std::optional unusable? If you are maintaining existing code using std::optional, how does this existing code follow the rules? – Richard Critten Jun 01 '23 at 09:38
  • If you need guaranteed validity then the function should return the value directly, not an optional. You can always add a `template [[nodiscard]] auto get_manadatory_value(const std::optional& optional)...` function and call that. (basically replacing your macro with a typesafe function) – Pepijn Kramer Jun 01 '23 at 09:38
  • 7
    Note that `std::optional::value()` is always checked. Calling `.value()` on an unset optional is defined behavior and throws exception. For unchecked access, consider using `.operator*()`. – Mestkon Jun 01 '23 at 09:41
  • 2
    If you really must protect against use of an unchecked optional, don't provide `std::optional` directly to client code. Instead, write another class (which may use `std::optional` privately, but not expose it) that only provides checked operations, and throws on any attempt to access a non-existent value. `std::optional` is specified in a way that relies on it being used correctly (e.g. `o.has_value()` is checked before calling `o.value()`). [Personally, I'd be more concerned about prospects of other developers not accepting responsibility if they don't check things properly]. – Peter Jun 01 '23 at 10:20
  • @Peter No need to check and throw, just return `o.value()` directly – as Mestkon highlighted, that already *is* checking and throwing – the argument for still wrapping is to hide the unchecked operators (`*` and `->`) away from the user so that it cannot circumvent calling `value`. – Aconcagua Jun 01 '23 at 11:12
  • In a general scenario, where it *is* meaningful to check explicitly (e.g. wanting to avoid rather expensive exception handling) then on success these operators should be used instead to avoid double-checking ;) – Aconcagua Jun 01 '23 at 11:19

1 Answers1

1

You shouldn't use value (or has_value) directly. Use transform and and_then for the single optional case, and the following for the multiple argument case.

template <typename T>
struct is_optional : std::false_type{};

template <typename T>
struct is_optional<std::optional<T>> : std::true_type{};

template <typename T>
concept Optional = is_optional<std::remove_cvref_t<T>>::value;

template <typename F, Optional... Opts>
auto transform(F&& f, Opts&&... opts)
{
    return (opts && ...) ? std::optional{std::invoke(std::forward<F>(f), *std::forward<Opts>(opts)...)} : std::nullopt;
}

template <typename F, Optional... Opts>
auto and_then(F&& f, Opts&&... opts)
{
    return (opts && ...) ? std::invoke(std::forward<F>(f), *std::forward<Opts>(opts)...) : std::nullopt;
}
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • I agree about not using value or has_value directly. Also, I'm unable to use C++17 features like the fold on the opts. I really like this solution though. – Richard Fabian Jun 01 '23 at 12:12
  • @RichardFabian oh, are you using boost::optional? [Adapted for C++14](https://coliru.stacked-crooked.com/a/40c103636a9bb9c2) – Caleth Jun 01 '23 at 12:47
  • No, but probably something similar. Ours works with C++11, but we are allowed to write with C++14 features now. – Richard Fabian Jun 02 '23 at 10:38