Является ли разница между двумя экземплярами constexpr указателей __func__ все еще constexpr?

14

Это действительный C ++?

int main() {
    constexpr auto sz = __func__ - __func__;
    return sz;
}

GCC и MSVC считают, что все в порядке, Clang считает, что это не так: Compiler Explorer .


Все компиляторы соглашаются, что с этим все в порядке: Compiler Explorer .

int main() {
    constexpr auto p = __func__;
    constexpr auto p2 = p;
    constexpr auto sz = p2 - p;
    return sz;
}

Clang снова не нравится этот, но другие в порядке с этим: Compiler Explorer

int main() {
    constexpr auto p = __func__;
    constexpr auto p2 = __func__;
    constexpr auto sz = p2 - p;
    return sz;
}

Что здесь? Я думаю, что арифметика на несвязанных указателях - неопределенное поведение, но __func__возвращает тот же самый указатель, нет? Я не уверен, поэтому я подумал, что могу это проверить. Если я правильно помню, std::equal_toможно сравнить несвязанные указатели без неопределенного поведения:

#include <functional>

int main() {
    constexpr std::equal_to<const char*> eq{};
    static_assert(eq(__func__, __func__));
}

Кланг считает, что eq(__func__, __func__)это не постоянное выражение, хотя std::equal_to::operator() является constexpr . Другие компиляторы не жалуются: Compiler Explorer


Clang тоже не скомпилирует. Жалуется, что __func__ == __func__не является константным выражением: Compiler Explorer

int main() {
    static_assert(__func__ == __func__);
}
Ayxan
источник
От Function_definition , __func__как если бы, static const char __func__[] = "function-name";и этот эквивалент принимается Демо ...
Jarod42
Интересно, что это работает, если вы инициализируете переменную constexpr __func__и используете ее в static_assert ...
florestan
@ Jarod42 Так это ошибка в Clang?
Ayxan
@florestan как это ? Это не скомпилируется с Clang либо. Мои 2-й и 3-й примеры в этом вопросе, как вы упомянули. Один компилируется, другой нет.
Ayxan
1
Смотрите также CWG1962 , который может __func__полностью удалить из оценки constexpr.
Дэвис Херринг

Ответы:

13

__func__в С ++ есть идентификатор. В частности, он ссылается на конкретный объект. Из [dcl.fct.def.general] / 8 :

Функция-локальная предопределенная переменная _­_­func_­_­определяется как определение формы

static const char __func__[] = "function-name";

где функция-name является строкой, определенной реализацией. Не указано, имеет ли такая переменная адрес, отличный от адреса любого другого объекта в программе.

Как локальная предопределенная переменная функции , это определение (как будто) появляется в начале функционального блока. Таким образом, любое использование __func__внутри этого блока будет ссылаться на эту переменную.

Что касается части «любой другой объект», переменная определяет объект. __func__называет объект, определенный этой переменной. Следовательно, внутри функции все __func__имена используют одну и ту же переменную. Не определено, является ли эта переменная отличным от других объектов.

То есть, если вы находитесь в функции с именем foo, и вы использовали литерал "foo"где-то еще в проблеме, для реализации не запрещено, чтобы переменная __func__также была тем же объектом, который "foo"возвращает литерал . То есть стандарт не требует, чтобы каждая функция, которая __func__появляется, должна хранить данные отдельно от строкового литерала.

Теперь правило C ++ «как будто» позволяет реализациям отклоняться от этого, но они не могут сделать это так, чтобы это можно было обнаружить. Таким образом, хотя сама переменная может иметь или не иметь адрес, отличный от других объектов, использование одной и __func__той же функции должно вести себя так, как если бы они ссылались на один и тот же объект.

Clang, похоже, не реализует __func__этот способ. Похоже, он реализован так, как будто он возвратил строковый литерал prvalue имени функции. Два разных строковых литерала не должны ссылаться на один и тот же объект, поэтому вычитать указатели на них - это UB. И неопределенное поведение в контексте постоянного выражения плохо сформировано.

Единственное, что заставляет меня сомневаться в том, что Clang на 100% ошибочен, это [temp.arg.nontype] / 2 :

Для нетипового шаблона-параметра ссылки или типа указателя значение константного выражения не должно ссылаться (или для типа указателя не должно быть адресом):

...

  • предопределенная _­_­func_­_переменная.

Видите, это, кажется, позволяет немного обмануть реализацию. То есть, хотя __func__технически может быть константным выражением, его нельзя использовать в параметре шаблона. Он обрабатывается как строковый литерал, хотя технически он является переменной.

Так что на каком-то уровне я бы сказал, что стандарт говорит с обеих сторон.

Николь Болас
источник
Итак, строго говоря, __func__может быть постоянным выражением во всех случаях в моем вопросе, верно? Так что код должен был скомпилироваться.
Ayxan
Как насчет «Не определено, имеет ли такая переменная адрес, отличный от адреса любого другого объекта в программе». часть? Неопределенное поведение означает недетерминизм в поведении абстрактной машины. Может ли это быть проблематичным для оценки constexpr? Что, если в первом случае __func__адрес совпадает с адресом другого объекта, а во втором __func__- нет? Конечно, это не означает, что адрес отличается между двумя экземплярами, но я все еще в замешательстве!
Йоханнес Шауб - Lit
@ JohannesSchaub-litb: « Как насчет« Не определено, имеет ли такая переменная адрес, отличный от адреса любого другого объекта в программе. »Часть? « А как насчет этого? __func__не макрос; это идентификатор, который называет конкретную переменную и, следовательно, конкретный объект. Следовательно, любое использование одной и __func__той же функции должно приводить к glvalue, который ссылается на тот же объект. Или, что более важно, это не может быть реализовано таким образом, чтобы это было не так.
Никол Болас
@Nicol, который относится к тому же объекту. Но этот объект может в один момент иметь тот же адрес, что и другой объект. И в другой момент не имеют. Я не говорю, что это проблема, но я просто напоминаю всем об этой возможности. И потом, в конце концов, я тоже могу ошибаться, поэтому я тоже говорю это в надежде на то, что меня исправят или подтвердят.
Йоханнес Шауб - Lit
@ JohannesSchaub-litb: « Но этот объект может в один момент иметь тот же адрес, что и другой объект». Это не разрешено в объектной модели C ++. Два объекта, ни один из которых не вложен в другой, не могут одновременно находиться в одном и том же хранилище. И у рассматриваемого объекта есть статическая продолжительность хранения, поэтому, если вы не используете размещение newна нем, он никуда не денется, пока программа не завершится.
Никол Болас