Constexpr if и static_assert
P0292R1 constexpr, если был включен, в пути для С++ 17. Кажется полезным (и может заменить использование SFINAE), но комментарий относительно static_assert
не сформирован, никакая диагностика, требуемая во ложной ветки, не пугает меня:
Disarming static_assert declarations in the non-taken branch of a
constexpr if is not proposed.
void f() {
if constexpr (false)
static_assert(false); // ill-formed
}
template<class T>
void g() {
if constexpr (false)
static_assert(false); // ill-formed; no
// diagnostic required for template definition
}
Я полагаю, что полностью запрещено использовать
static_assert
внутри constexpr, если (по крайней мере, ложная/невозвращенная ветвь, но это на практике означает, что это не безопасная или полезная вещь).
Как это происходит из стандартного текста? Я не вижу упоминания static_assert
в формулировке предложения, а функции С++ 14 constexpr разрешают static_assert
(подробности в cppreference: constexpr).
Скрывается ли это новое предложение (после 6.4.1)?:
Когда выражение constexpr if появляется в шаблонизированном объекте, во время создания охватывающего шаблона или общей лямбды, отбрасываемый оператор не создается.
Оттуда я предполагаю, что также запрещено, не требуется диагностика, для вызова других функций constexpr (template), которые где-то по графику вызовов могут вызывать static_assert
.
Нижняя строка:
Если мое понимание верное, не делает ли это довольно жесткое ограничение безопасности и полезности constexpr if
, как мы должны были бы знать (из документации или проверки кода) о любом использовании static_assert
? Неужели мои заботы неуместны?
Update:
Этот код компилируется без предупреждения (clang head 3.9.0), но для моего понимания плохо сформирован, никакой диагностики не требуется. Действительно или нет?
template< typename T>
constexpr void other_library_foo(){
static_assert(std::is_same<T,int>::value);
}
template<class T>
void g() {
if constexpr (false)
other_library_foo<T>();
}
int main(){
g<float>();
g<int>();
}
Ответы
Ответ 1
Это говорит о хорошо установленном правиле для шаблонов - том же правиле, которое позволяет компиляторам диагностировать template<class> void f() { return 1; }
. [temp.res]/8 с новым изменением bolded:
Программа плохо сформирована, не требуется диагностика, если:
- для шаблона или подстановки не может быть создана действительная специализация оператора
constexpr if
([stmt.if]) в пределах шаблон, и шаблон не создается, или - [...]
Нельзя создать достоверную специализацию для шаблона, содержащего static_assert
, условие которого не зависит и оценивается как false
, поэтому программа является плохо сформированной NDR.
static_assert
с зависимым условием, которое может оцениваться как true
по крайней мере для одного типа, не затрагивается.
Ответ 2
Изменить: я держу этот ответ с примерами и более подробными объяснениями недоразумений, которые приводят к этим вопросам. Краткий ответ ТС достаточно строго.
static_assert
предложение и о static_assert
в текущем черновике, и я пришел к выводу, что мои опасения были ошибочными. Прежде всего, акцент здесь должен быть сделан на определении шаблона.
плохо сформирован; для определения шаблона не требуется диагностика
Если создается экземпляр шаблона, любой static_assert
срабатывает, как и ожидалось. Предположительно, это хорошо согласуется с утверждением, которое я цитировал:
... выброшенное выражение не создается.
Это немного расплывчато для меня, но я прихожу к выводу, что это означает, что шаблоны, встречающиеся в отброшенном выражении, не будут созданы. Однако другой код должен быть синтаксически действительным. static_assert(F)
, [где F ложно, буквально или значение constexpr] внутри отброшенного предложения if constexpr
, таким образом, все еще будет "кусаться", когда static_assert
экземпляр шаблона, содержащего static_assert
. Или (не обязательно, во власти компилятора) уже при объявлении, если известно, что оно всегда ложно.
Примеры: (живая демонстрация)
#include <type_traits>
template< typename T>
constexpr void some_library_foo(){
static_assert(std::is_same<T,int>::value);
}
template< typename T>
constexpr void other_library_bar(){
static_assert(std::is_same<T,float>::value);
}
template< typename T>
constexpr void buzz(){
// This template is ill-formated, (invalid) no diagnostic required,
// since there are no T which could make it valid. (As also mentioned
// in the answer by T.C.).
// That also means that neither of these are required to fire, but
// clang does (and very likely all compilers for similar cases), at
// least when buzz is instantiated.
static_assert(! std::is_same<T,T>::value);
static_assert(false); // does fire already at declaration
// with latest version of clang
}
template<class T, bool IntCase>
void g() {
if constexpr (IntCase){
some_library_foo<T>();
// Both two static asserts will fire even though within if constexpr:
static_assert(!IntCase) ; // ill-formated diagnostic required if
// IntCase is true
static_assert(IntCase) ; // ill-formated diagnostic required if
// IntCase is false
// However, don't do this:
static_assert(false) ; // ill-formated, no diagnostic required,
// for the same reasons as with buzz().
} else {
other_library_bar<T>();
}
}
int main(){
g<int,true>();
g<float,false>();
//g<int,false>(); // ill-formated, diagnostic required
//g<float,true>(); // ill-formated, diagnostic required
}
Стандартный текст на static_assert
очень короткий. В стандарте это способ сделать программу некорректной с диагностикой (как также указывал @immibis):
7.6... Если значение выражения при таком преобразовании равно true, объявление не имеет никакого эффекта. В противном случае программа некорректна, и полученное диагностическое сообщение (1.4) должно включать текст строкового литерала, если он указан...