Условная компиляция шаблонов
Я пытаюсь получить static_assert, чтобы помочь мне избежать нулевых указателей в C++11.
Кажется, проблема в том, что C++ 11 требует, чтобы компилятор компилировал шаблоны, даже если они не созданы.
У меня есть следующий код:
#include <type_traits>
template<typename T, typename... Us>
std::enable_if_t< std::is_constructible<T, Us...>::value == true, T * >
create_if_constructible(Us... args) { return new T(args...); }
template<typename T, typename... Us>
std::enable_if_t< std::is_constructible<T, Us...>::value == false, T * >
create_if_constructible(Us... args) {
static_assert( false, "Class T constructor does not match argument list.");
return nullptr;
}
struct ClassA {
ClassA(int a, string b) {}
};
void foo() {
ClassA *a = create_if_constructible<ClassA>(1, "Hello");
// ClassA *b = create_if_constructible<ClassA>(1, "Hello", "world"); // I want compile time error here.
}
Я хотел бы это скомпилировать без ошибок. Но static_assert компилируется и выдает ошибку времени компиляции.
Только если второй экземпляр ClassA находится в коде, он должен дать мне ошибку времени компиляции.
2 ответа
Стандарт разрешает, но не требует, чтобы компиляторы диагностировали шаблоны, для которых не может быть сгенерировано действительного экземпляра. Это может варьироваться от простых синтаксических ошибок до вашего примера константы false
выражение в static_assert
, §14.6 [temp.res]/p8:
Если для шаблона не может быть сгенерировано никакой действительной специализации, и этот шаблон не создан, шаблон неправильно сформирован, диагностика не требуется.
Я довольно сбит с толку всей этой техникой SFINAE. Просто
template<typename T, typename... Us>
T* create_if_constructible(Us... args) { return new T(args...); }
уже отказывается компилировать, если T
не может быть построено из данного параметра, поэтому я не уверен, как это сложное обходное окружение поможет вам "избежать нулевых указателей".
Несмотря на это, простой способ сделать выбор второго шаблона функции ошибкой во время компиляции - явно удалить его.
template<typename T, typename... Us>
std::enable_if_t< std::is_constructible<T, Us...>::value == false, T * >
create_if_constructible(Us... args) = delete;
В качестве альтернативы, если вы неравнодушны к static_assert
s, возможно, из-за пользовательского сообщения об ошибке, вы должны убедиться, что теоретически существует способ создать действительный экземпляр вашего шаблона. Это означает, что 1) кем вы являетесь static_assert
должно зависеть от аргумента шаблона, и 2) теоретически должен быть способ, чтобы условие было true
, Простой способ - использовать вспомогательный шаблон:
template<class> class always_false : std::false_type {};
template<typename T, typename... Us>
std::enable_if_t< std::is_constructible<T, Us...>::value == false, T * >
create_if_constructible(Us... args) {
static_assert( always_false<T>::value, "Class T constructor does not match argument list.");
return nullptr;
}
Ключевым моментом здесь является то, что компилятор не может предположить, что always_false<T>::value
всегда false
потому что всегда возможно, что есть специализация позже, которая устанавливает его true
и поэтому нельзя отклонять это во время определения шаблона.
Во всех стандартах C++ шаблоны компилируются в два этапа. Второй этап - создание экземпляра, но компиляция также может завершиться неудачей на этапе 1. В частности, синтаксические ошибки обнаруживаются на этапе 1.
В вашем случае, более простое решение - исключить тело второго экземпляра.
Другое решение заключается в использовании T
в static_assert
поэтому компилятор должен отложить оценку до фазы 2. Тривиально: static_assert(sizeof(T)==0,