Вопрос или проблема
Я пытаюсь написать специализированный конструктор, который бы принимал целочисленный аргумент и преобразовывал его в перечисление. Моя попытка приведена ниже, но она завершается ошибкой. Другие шаблонные кандидаты отклоняются, и это правильно. Есть идеи, как это исправить?
#include <type_traits>
template<typename T>
class SomeType
{
public:
T val;
template <typename U>
SomeType<T>(typename std::enable_if_t<std::is_integral<U>::value>::type _u) :
val(T(_u)) { }
SomeType<T>(const T& _t) : val(_t) { }
SomeType<T>(const SomeType<T>& _t) : val(_t.val) { }
SomeType<T>() : val() { }
};
void foo()
{
typedef enum { ala=0, ola} MyEnumType;
SomeType<int> s1; // ОК
SomeType<int> s2(2); // ОК
SomeType<MyEnumType> e1; // ОК
SomeType<MyEnumType> e2(MyEnumType::ola); // ОК
SomeType<MyEnumType> e3(MyEnumType(0)); // ОК
SomeType<MyEnumType> e4(0);
// ОШИБКА:
// кандидат: template<class U> SomeType<T>::SomeType(typename std::enable_if<std::is_integral<U>::value, void>::type::type)
// SomeType<T>(typename std::enable_if_t<std::is_integral<U>::value>::type _u) : val(NativeType(_u)) { }
// ^~~~~~~~~~~
// вывод шаблонных аргументов/замена не удалась:
// не удалось вывести шаблонный параметр 'U'
}
Я пробовал разные шаблоны использования для enable_if_t, но ни один из них не сработал.
Ответ или решение
Давайте разберем, как можно исправить вашу реализацию шаблонного конструктора с использованием enable_if_t
, чтобы он корректно принимал целочисленные аргументы и конвертировал их в перечисления. Ваша текущая реализация сталкивается с проблемой, связанной с тем, что компилятор не может вывести тип шаблона U
из конструктора, поскольку вы используете typename std::enable_if_t
в качестве параметра.
Решение проблемы
Для начала следует изменить сигнатуру конструктора. Вместо использования typename std::enable_if_t
, мы будем использовать статическую проверку в самом конструкторе с помощью enable_if
и сделаем его зависимым от параметра U
. Это позволит компилятору правильно разрешать U
в зависимости от переданного аргумента.
Вот как это можно сделать:
#include <type_traits>
template<typename T>
class SomeType
{
public:
T val;
// Специализированный конструктор для int
template <typename U, typename = typename std::enable_if<std::is_integral<U>::value>::type>
SomeType(U _u) : val(T(_u)) { }
// Конструктор для типа T
SomeType(const T& _t) : val(_t) { }
SomeType(const SomeType<T>& _t) : val(_t.val) { }
SomeType() : val() { }
};
void foo()
{
enum MyEnumType { ala = 0, ola }; // Определение перечисления
SomeType<int> s1; // OK
SomeType<int> s2(2); // OK
SomeType<MyEnumType> e1; // OK
SomeType<MyEnumType> e2(MyEnumType::ola); // OK
SomeType<MyEnumType> e3(MyEnumType(0)); // OK
SomeType<MyEnumType> e4(0); // Теперь компилируется корректно
}
Объяснение изменений
-
Тип U и enable_if: Во втором параметре шаблонного конструктора мы добавили
typename = typename std::enable_if<std::is_integral<U>::value>::type
. Это позволяет нам использоватьU
, который будет выведен компилятором на основе переданного аргумента. -
Отказ от
typename
передstd::enable_if_t
: Мы используем структуруenable_if
в стиле SFINAE (Substitution Failure Is Not An Error), чтобы корректно обрабатывать только целочисленные типы. -
Конструктор для T: Конструктор для типа
T
остался без изменений, он по-прежнему позволяет инициализировать объектSomeType
значениями, которые соответствуют типуT
.
Вывод
Теперь данный вариант конструктора компилируется и позволяет конвертировать целочисленные значения в перечисляемые типы. Этот подход минимизирует потенциальные ошибки и явным образом задает условия для использования конструктора. Надеюсь, этот пример поможет вам должным образом реализовать необходимую функциональность в вашем коде.