Небезопасный, "noexcept" и без накладной способ доступа к "std:: variant"

std::variant предоставляет следующие функции доступа:

  • std::get_if: возьмите указатель на variant, верните указатель на альтернативу.

    template <std::size_t I, typename... Ts> 
    auto* std::get_if(std::variant<Ts...>* pv) noexcept;
    
    • Если pv не является нулевым указателем и pv->index() == I, возвращает указатель на значение, сохраненное в варианте, на которое указывает pv. В противном случае возвращает значение нулевого указателя.

      Это означает, что реализация get_if выглядит примерно так:

      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: обратитесь к variant, верните ссылку на альтернативу, throw на недопустимый доступ.

    template <std::size_t I, typename... Ts>
    auto& std::get(std::variant<Ts...>& v);
    
    • Если v.index() == I, возвращает ссылку на значение, хранящееся в v. В противном случае выбрасывается std::bad_variant_access.

      Это означает, что реализация get выглядит примерно так:

      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>();
      }
      

Я хочу функцию небезопасного доступа, которая:

  • Является noexcept.

  • Делает ссылку на variant, избегая проверки pv == nullptr.

  • Поведение undefined, если v.index() != I.

Почему? Потому что могут быть ситуации, когда я на 100% уверен, что конкретный экземпляр variant содержит определенный тип кода. Кроме того, было бы полезно при написании общего кода, который уже отдельно проверен v.index() != I (например, написания моего собственного visit).

Пример реализации:

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

Есть ли что-то подобное в стандарте? Я не мог найти его. Если нет, можно реализовать для std::variant, или мне нужно выполнить собственную реализацию variant?

Ответ 1

Как указано @T.C. в комментариях ваша первая и третья просьба несовместимы друг с другом. Это было описано в N3279 под названием "Консервативное использование noexcept в библиотеке".

Существуют по существу два класса контрактов: узкие и широкие. Контракт широкий для функции или операции не определяет поведение undefined. Такой договор не имеет никаких предварительных условий. В стандартной библиотеке отмечены noexcept только функции с широкими контрактами.

OTOH, контракт узкий - это контракт, который не является широким. Узкие контракты для функций или операций приводят к undefined поведению при вызове таким образом, который нарушает документированный контракт. Они не могут быть отмечены noexcept. Вместо этого лучшее, что вы можете ожидать, это то, что они задокументированы как "Броски: ничего".

Похоже, что вам не повезло, и в текущих предложениях для std::variant такой беспрепятственный доступ не предоставляется.

Ответ 2

Думаю, вам нужно реализовать весь вариант самостоятельно. Хотя неограниченные союзы могут быть полезны - они, по крайней мере, решают ставить несколько типов в одном и том же месте с проблемой обработки выравнивания.