Подтвердить что ты не робот

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>();
}
4b9b3361

Ответ 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) должно включать текст строкового литерала, если он указан...