Условная компиляция шаблонов

Я пытаюсь получить 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_asserts, возможно, из-за пользовательского сообщения об ошибке, вы должны убедиться, что теоретически существует способ создать действительный экземпляр вашего шаблона. Это означает, что 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,

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