Unsafe, `noexcept` and no-overhead way of accessing `std::variant`
Asked Answered
G

2

13

std::variant provides the following access functions:

  • std::get_if: take pointer to variant, return pointer to alternative.

    template <std::size_t I, typename... Ts> 
    auto* std::get_if(std::variant<Ts...>* pv) noexcept;
    
    • If pv is not a null pointer and pv->index() == I, returns a pointer to the value stored in the variant pointed to by pv. Otherwise, returns a null pointer value.

      This means that get_if's implementation roughly looks like this:

      template <std::size_t I, typename... Ts> 
      auto* std::get_if(std::variant<Ts...>* pv) noexcept
      {
          if(pv == nullptr) return nullptr;
          if(pv->index() != I) return nullptr;
          return &(pv->real_get<I>());
      }
      
  • std::get: take reference to variant, return reference to alternative, throw on invalid access.

    template <std::size_t I, typename... Ts>
    auto& std::get(std::variant<Ts...>& v);
    
    • If v.index() == I, returns a reference to the value stored in v. Otherwise, throws std::bad_variant_access.

      This means that get's implementation roughly looks like this:

      template <std::size_t I, typename... Ts> 
      auto& std::get(std::variant<Ts...>& v)
      {
          if(v.index() != I) throw std::bad_variant_access{};
          return v.real_get<I>();
      }
      

I want an unsafe access function that:

  • Is noexcept.

  • Takes a reference to a variant, avoiding any pv == nullptr check.

  • Has undefined behavior if v.index() != I.

Why? Because there may be some situations where I am 100% sure that a particular variant instance contains a specific type in a code path. Also, it would be useful when writing generic code that already separately checked v.index() != I (e.g. writing my own visit).

Example implementation:

template <std::size_t I, typename... Ts> 
auto& unsafe_get(std::variant<Ts...>& v)
{
    return v.real_get<I>();
}

Is there something like this in the standard? I couldn't find it. If not, is this possible to implement for std::variant, or do I need to roll out my own variant implementation?

Gigolo answered 5/2, 2017 at 12:35 Comment(10)
I don't think the standard library will provide/allow such a type-unsafe function.Barracks
Maybe worth asking on std-discuss. This could always be proposed as a future extension.Outlast
Why does the noexcept condition matter that much, or at all? How have you tested this?Cakewalk
Use a visitor. Move the code part which makes you 100% sure that a given type is in the variant into the visitor. It might turn out that you don't need that part of code or need it just as an assert. This makes your code way faster.Arrogance
LWG doesn't put noexcept on things that can lead to UB, so #1 and #3 are de facto mutually exclusive.Freiman
I also don't see why *std::get_if<I>(&my_variant) cannot be optimized down to what you want, in principle.Freiman
@T.C.: Even if inlining allows the compiler to optimize it down to one conditional check, it cannot get rid of the check to see if I matches the current index. There's no way to statically know that for any particular my_variant.Koval
@NicolBolas But in the case of an index mismatch, the resulting null pointer dereference is UB, so I don't see why the compiler can't assume that it never happens and optimize away the index comparison.Freiman
@T.C.: It could. But since there's no guarantee that it will, and there's no long history of compilers offering such a guarantee, there's no reason to rely on it.Koval
@NicolBolas That's why I said "in principle".Freiman
A
7

As pointed out by @T.C. in the comments, your first and third desiderata are mutually incompatible. This was spelled out in N3279, titled "Conservative use of noexcept in the Library".

There are essentially two classes of contracts: narrow and wide. A wide contract for a function or operation does not specify any undefined behavior. Such a contract has no preconditions. Only functions with wide contracts are marked noexcept in the Standard Library.

OTOH, a narrow contract is a contract which is not wide. Narrow contracts for a functions or operations result in undefined behavior when called in a manner that violates the documented contract. They cannot be marked noexcept. Instead, the best you can expect is that they are documented as "Throws: Nothing."

It appears that you are out of luck and no such unchecked access is provided in the current proposals for std::variant.

Archaeornis answered 7/2, 2017 at 8:33 Comment(0)
E
2

I think you have to implement the whole variant on your own. Though unrestricted unions can be helpful - they at least solve putting multiple types in the same location with alignment issue handled.

Enrobe answered 5/2, 2017 at 13:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.