C++17: использование std::option для оценки, содержит ли enum значение

Я хотел бы проверить во время компиляции, если различные перечисления содержат данное значение, поэтому я написал следующее:

#include <optional>

enum class test_enum : int {
    VALUE_0 = 0,
    VALUE_1 = 1
};

// Template function to perform check
template<typename T>
constexpr std::optional<T> from_int(int value)
{
    static_assert(false, __FUNCTION__ " not implemented for this type; see build output");
    return std::optional<T>();
}

// Specialization for test_enum
template<>
constexpr std::optional<test_enum> from_int(int value)
{
    switch (value) {
        case static_cast<int>(test_enum::VALUE_0) :
            return test_enum::VALUE_0;
        case static_cast<int>(test_enum::VALUE_1):
            return test_enum::VALUE_1;
        default:
            return std::optional<test_enum>();
    }
}

int main(int argc, char* argv[])
{
    static_assert(from_int<test_enum>(1));

    return 0;
}

Используя Visual Studio 2017 (версия 15.8.6), код успешно компилируется без ошибок в выводе. Тем не менее, окно ошибки показывает

E0028: expression must have a constant value" at line 30. (the first line of main)

а также

"std::_Optional_construct_base<test_enum>::_Optional_construct_base(std::in_place_t, _Types &&..._Args) [with _Types=<test_enum>]" (declared implicitly) is not defined)".

Есть намеки на то, почему это так? Я могу игнорировать E0028, но я бы предпочел не делать этого, если это возможно.

РЕДАКТИРОВАТЬ: удаление static_assert из from_int не изменит ошибку.

3 ответа

Решение

Данный код компилируется без предупреждений или ошибок, используя cl v19.15.26726 (Visual Studio версии 15.9.0-pre.1.0)

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

[Действительность шаблона может быть проверена до любого экземпляра. [Примечание: Знание того, какие имена являются именами типов, позволяет проверять синтаксис каждого шаблона таким образом. - примечание конца] Программа некорректна, диагностика не требуется, если:

<...>

(8.4) гипотетическое создание шаблона сразу после его определения было бы некорректным из-за конструкции, которая не зависит от параметра шаблона...] 1

Чтобы сделать его правильно сформированным, не используйте static_assert(false), Вместо этого используйте следующий прием (компилируется с GCC 7 и CLang 7):

#include <optional>

enum class test_enum : int {
    VALUE_0 = 0,
    VALUE_1 = 1
};

template<typename T> 
constexpr bool false_t = false;

// Template function to perform check
template<typename T>
constexpr std::optional<T> from_int(int value)
{
    static_assert(false_t<T>, "Not implemented for this type; see build output");
    return std::optional<T>();
}

// Specialization for test_enum
template<>
constexpr std::optional<test_enum> from_int<test_enum>(int value)
{
    switch (value) {
        case static_cast<int>(test_enum::VALUE_0) :
            return test_enum::VALUE_0;
        case static_cast<int>(test_enum::VALUE_1):
            return test_enum::VALUE_1;
        default:
            return std::optional<test_enum>();
    }
}

int main()
{
    static_assert(from_int<test_enum>(1));
}

Гораздо лучше использовать диспетчеризацию тегов, чем специализацию шаблонов в 99/100 случаях.

#include <optional>

enum class test_enum : int {
    VALUE_0 = 0,
    VALUE_1 = 1
};

template<class T> struct tag_t {};

namespace from_int_details {
  template<class T>
  std::optional<T> from_int_impl( tag_t<T>, int value ) = delete;
}
template<class T>
std::optional<T> from_int( int value ) {
  using namespace from_int_details;
  return from_int_impl( tag_t<T>{}, value );
}

// Overload for test_enum, same namespace as test_enum *or* in from_int_details:
constexpr std::optional<test_enum> from_int_impl(tag_t<test_enum>, int value)
{
    switch (value) {
        case static_cast<int>(test_enum::VALUE_0) :
            return test_enum::VALUE_0;
        case static_cast<int>(test_enum::VALUE_1):
            return test_enum::VALUE_1;
        default:
            return std::optional<test_enum>();
    }
}

int main()
{
    static_assert(from_int<test_enum>(1));
}

здесь люди простираются from_int написав constexpr optional<the_enum_type> from_int_impl( tag_t<the_enum_type>, int ) либо в пространстве имен the_enum_type (так что его можно найти через ADL) или для перечислений, где это невозможно (например, перечисления в std), в from_int_details Пространство имен. Или пространство имен tag_t,

Вот этот код компилируется в MSVC 2017, версия компилятора 19.10.

Другие вопросы по тегам