8

For some standard library classes, access to parts of their contents may legitimately fail. Usually you have the choice between some potentially throwing method an one that is marked noexcept. The latter spares the check on the precondition, so if you want to take the responsibility yourself, you can. This can be used under circumstances where using exceptions are not permitted or when fixing a performance bottleneck.

Example 1: std::vector element access:

std::vector<int> vec;
vec.at(n) // throws std::out_of_range
vec[n] // potentially UB, thus your own responsibility

Example 2: std::optional access:

std::optional<int> optn;
optn.value() // throws std::bad_optional_access
*optn // potentially UB, thus your own responsibility

Now on to std::variant. Directly accessing an alternative somewhat follows this pattern:

std::variant<std::string, int> var;
std::get<int>(var) // potentially throwing std::bad_variant_access
*std::get_if<int>(&var) // potentially UB, thus your own responsibility

But this time the signature changes, we have to inject * and &. The downside of this is that we don't get automatic move semantics. One more thing to keep in your mind...

But it gets worse if you have a look at std::visit(Visitor&& vis, Variants&&... vars). There is no noexcept alternative for it, although it only throws

if any variant in vars is valueless_by_exception.

This means for visiting variants you cannot choose to take the responsibility yourself, and if you have no choice and must avoid exceptions, you cannot visit std::variants at all with standard tooling! (apart from the terrible workaround of switching on variant::index())

To me, this looks like a pretty bad design oversight... or there a reason for this? And in case I'm right about the oversight, is there an initiative to fix this in the standard?

Tobi
  • 2,591
  • 15
  • 34

1 Answers1

2

This means for visiting variants you cannot choose to take the responsibility yourself

Sure you can. The "valueless-by-exception" state can only happen if you assign or emplace a value into an existing variant. Furthermore, by definition, it can only happen if an exception is actually thrown during these processes. That is not a state that ever just happens to a random variant.

If you take responsibility to ensure that you either never emplace/assign to a variant, or that the types you use never throw in those circumstances, or that you respond to any exceptions from doing so in such a way that the variant that provoked it is not being talked to (ie: if bad_alloc is thrown, your application doesn't catch it; it just shuts down), then you don't have to care about this possibility.

Basically, if you're already coding to avoid exceptions, the non-noexcept status of visit is irrelevant. No variant will ever get into the "valueless-by-exception" unless an exception is thrown.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • No: if exceptions are disabled, `std::visit` does not exist in the standard library. – Tobi Dec 27 '18 at 14:44
  • @Tobi: I don't know of any compiler that actively *removes* non-`noexcept` functions when you disable exception handling. My general understanding is that turning off exception handling either removes `throw` statements or causes them to terminate the application. – Nicol Bolas Dec 27 '18 at 14:44
  • For example, Apple Clang doesn't let you throw `std::bad_variant_access` if you target macOS 10.13 and earlier (it's an issue of the C++ runtime, see [here](https://stackoverflow.com/questions/52310835/xcode-10-call-to-unavailable-function-stdvisit/53868971)). Thus `std::visit` does not exist in that case. – Tobi Dec 27 '18 at 14:49
  • @Tobi: But that's not an issue of disabling exceptions; it's an issue of a incomplete C++17 runtime. Basically, I'm saying that it's the fault of how iOS/MacOS handles its C++ runtime, not the fault of the standard. – Nicol Bolas Dec 27 '18 at 14:52
  • It's just one example. You might want to spare the extra runtime check and the generated exception-throwing code for any reason. In all the other examples the choice is yours, but not for `std::visit`. – Tobi Dec 27 '18 at 14:56
  • @Tobi: The runtime check happens essentially by default; somewhere in `visit`, it has to convert the index into a piece of code to get that value and call your visitation functor. As such, the "valueless-by-exception" state is just another state; it doesn't cost anything at runtime. There's just a different index that gets converted into a `throw`. The point being that it's not a big deal. – Nicol Bolas Dec 27 '18 at 15:05
  • 1
    That's not true, look at the libc++ [implementation](https://github.com/llvm-mirror/libcxx/blob/master/include/variant#L1537): It checks on `valueless_by_exception()` and then passes on to the actual implementation, which is noexcept. – Tobi Dec 27 '18 at 15:13
  • @Tobi: That's not how it *has to be* implemented; that's merely how it is implemented in some cases. – Nicol Bolas Dec 27 '18 at 15:53