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.