Почему я могу вызывать функцию, не относящуюся к constexpr, внутри функции constexpr?

Рассмотрим следующий код:

#include <stdio.h>

constexpr int f()
{
    return printf("a side effect!\n");
}

int main()
{
    char a[f()];
    printf("%zd\n", sizeof a);
}

Я ожидал, что компилятор будет жаловаться на вызов printf внутри f, потому что f должен быть constexpr, а printf — нет. Почему программа компилирует и печатает 15?


person fredoverflow    schedule 04.03.2014    source источник
comment
clang 3.5 выдает ошибку: note: non-constexpr function 'printf' cannot be used in a constant expression   -  person Shafik Yaghmour    schedule 04.03.2014
comment
Может быть, ваш компилятор разрешает VLA? Это не удается на GC 4.8.2 с разумными флагами компилятора. error: ISO C++ forbids variable length array 'a' [-Wvla]   -  person juanchopanza    schedule 04.03.2014
comment
@GabiMe Какой бы компилятор ни использовал ideone :)   -  person fredoverflow    schedule 04.03.2014
comment
На gcc 4.8.2 с -pedantic выдает ошибку. Судя по всему, gcc рассматривает это как VLA и никогда не пытается проверить, действительно ли это constexpr. Попытка использовать f() в качестве аргумента шаблона, отличного от типа, завершится ошибкой.   -  person pmr    schedule 04.03.2014
comment
@pmr Я бы сказал, что он проверяет и обнаруживает, что это не constexpr. Затем он жалуется на VLA, потому что это то, что он поддерживает по умолчанию.   -  person juanchopanza    schedule 04.03.2014
comment
@juanchopanza Я думаю, что это сводится к следующему: когда следует отклонять недопустимый constexpr: (а) всегда, как это делает clang, (б) когда требуется его оценка во время компиляции?   -  person pmr    schedule 04.03.2014
comment
@juanchopanza Я согласен. clang, кажется, очень хочет отклонить это, и это, вероятно, должно быть предупреждением, а не ошибкой. Это должно быть ошибкой, когда f() используется как constexpr. К сожалению, я не могу легко подтвердить это цитатой.   -  person pmr    schedule 04.03.2014
comment
@pmr На самом деле, я думаю, ты прав. Я думал, что вызов f() допустим в контексте, отличном от constexpr. Я должен проверить это, хотя.   -  person juanchopanza    schedule 04.03.2014
comment
@pmr Похоже на ошибку в GCC. Если я заменю вызов printf вызовом int foo() {return 42;}, он не скомпилируется. То же самое следует сделать с printf, потому что constexpr тоже.   -  person juanchopanza    schedule 04.03.2014
comment
@juanchopanza: Возможно, это происходит из-за того, что strlen в GCC для символьного литерала равно constexpr, а GCC заменяет printf на puts (или puts+strlen, если вы используете возвращаемое значение), если строка формата не содержит спецификаторов формата. Таким образом, помимо очевидного побочного эффекта, можно было поверить, что это очень хорошая constexpr функция.   -  person Damon    schedule 04.03.2014
comment
@Damon Дэймон хммм, я не думаю, что компилятору разрешено это делать ... и если мы используем это constexpr int x = f(); в gcc, это выдает ошибку.   -  person Shafik Yaghmour    schedule 04.03.2014
comment
@pmr зависит от того, в этом случае стандарт говорит, что он неправильно сформирован, и диагностика не требуется.   -  person Shafik Yaghmour    schedule 04.03.2014
comment
@ShafikYaghmour: я согласен с тем, что это, безусловно, незаконно, но я вижу, как компилятор может поверить в это из-за этих замен. constexpr int x = strlen("foo"); прекрасно компилируется, и если вы разберете бинарный файл, вы нигде не найдете ни одного вызова strlen, только литерал (при условии, что вы его используете, поэтому он не оптимизирован). По иронии судьбы, это не верно для встроенных макросов, таких как __func__, которые для нас с вами совершенно очевидно являются constexpr, но компилятор будет утверждать, что это не так.   -  person Damon    schedule 04.03.2014
comment
@ Деймон, да, это хорошо для размышлений, если вы используете -fno-builtin, все это становится ошибками в gcc. Так что это имеет больше смысла, я все еще думаю, что компилятору не разрешено это делать.   -  person Shafik Yaghmour    schedule 04.03.2014
comment
@Damon Я опубликовал дополнение вопрос о том, разрешено ли gcc делать это или нет.   -  person Shafik Yaghmour    schedule 05.03.2014
comment
@ShafikYaghmour Не стесняйтесь переотмечать.   -  person fredoverflow    schedule 05.03.2014
comment
Я знаю, что это поздно, но обычно я полагаюсь на использование std::array<int, f()> a; для проверки константных выражений, а не VLA. Пример   -  person    schedule 05.03.2014
comment
Я обновил свой ответ, чтобы отразить некоторую новую информацию и некоторую информацию, которую я должен был добавить тогда, но забыл.   -  person Shafik Yaghmour    schedule 05.01.2015


Ответы (1)


Программа имеет неправильный формат и не требует диагностики в соответствии с проект стандарта C++11, раздел 7.1.5 Спецификатор constexpr, параграф 5, в котором говорится:

Для функции constexpr, если не существует таких значений аргументов функции, при которых подстановка вызова функции приводила бы к константному выражению (5.19), программа является некорректной; диагностика не требуется.

и приводит следующий пример:

constexpr int f(bool b)
  { return b ? throw 0 : 0; } // OK
constexpr int f() { return f(true); } // ill-formed, no diagnostic required

и параграф 2 раздела 5.19 гласит:

Условное-выражение является основным константным выражением, если только оно не включает одно из следующего в качестве потенциально оцениваемого подвыражения [...]

и включает:

— вызов функции, отличной от конструктора constexpr для литерального класса или функции constexpr [Примечание: разрешение перегрузки (13.3) применяется как обычно — примечание в конце];

Мы, вероятно, предпочли бы диагностику в этом случае, это может быть просто оплошность, у меня есть отчет об ошибке для аналогичной ситуации, когда gcc не выдает ошибку, но мы, вероятно, хотели бы, чтобы она: Разрешена ли компилятору свобода действий в том, что он считает неопределенным поведением в постоянное выражение?.

Обновить

Использование флага -fno-builtin приведет к тому, что gcc выдаст следующую ошибку:

 error: call to non-constexpr function 'int printf(const char*, ...)'
 return printf("a side effect!\n");
                                 ^

Таким образом, gcc считает это неправильным, он просто игнорирует его, когда использует встроенную версию printf.

Хотя несколько непоследовательно использование -pedantic приводит к следующему предупреждению:

warning: ISO C++ forbids variable length array 'a' [-Wvla]
 char a[f()];
           ^

Обратите внимание, что использование f() для инициализации переменной constexpr:

constexpr int x = f() ;

выдает ошибку:

error: 'printf(((const char*)"a side effect!\012"))' is not a constant expression

Обратите внимание, что в более общем случае компилятору не разрешается помечать стандартные библиотечные функции как constexpr , если это явно не разрешено стандарт.

person Shafik Yaghmour    schedule 04.03.2014