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

Почему нам нужно отмечать функции как constexpr?

С++ 11 позволяет использовать функции, объявленные с помощью спецификатора constexpr, в постоянных выражениях, таких как аргументы шаблона. Существуют строгие требования к тому, что разрешено constexpr; по существу такая функция инкапсулирует только одно подвыражение и ничего больше. (Изменить: это расслаблено в С++ 14, но вопрос стоит.)

Зачем требовать ключевое слово? Что получается?

Это помогает выявить намерение интерфейса, но не подтверждает этого намерения, гарантируя, что функция может использоваться в постоянных выражениях. После написания функции constexpr программист должен:

  • Напишите тестовый пример или иным образом убедитесь, что он действительно используется в постоянном выражении.
  • Укажите, какие значения параметров действительны в контексте константного выражения.

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


Короче: Может ли быть нежелательный эффект на языке, если constexpr в объявлениях функций просто необязательно? Или будет ли вообще эффект какой-либо действующей программы?

4b9b3361

Ответ 1

Когда я нажал Ричард Смит, автор Clang, он объяснил:

Ключевое слово constexpr имеет полезность.

Это влияет на создание экземпляра функции шаблона функции (возможно, необходимо создать экземпляр функции шаблона функции constexpr, если они вызваны в неоцененных контекстах, то же самое не относится к функциям non-constexpr, поскольку вызов к одному никогда не может быть частью постоянного выражения). Если мы удалим значение ключевого слова, нам нужно будет создать экземпляр более специализированных функций раньше, на всякий случай, когда вызов будет постоянным выражением.

Это сокращает время компиляции, ограничивая набор вызовов функций, реализация которых требуется для оценки во время перевода. (Это имеет значение для контекстов, где реализации необходимы для проверки оценки постоянных выражений, но это не ошибка, если такая оценка не удалась - в частности, инициализаторы объектов статической продолжительности хранения.)

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

Функциональная реализация - это рекурсивная процедура. Выполнение функции приводит к созданию экземпляров функций и классов, которые он использует, независимо от аргументов для любого конкретного вызова.

Если что-то пошло не так, создавая экземпляр этого дерева зависимостей (возможно, при значительных затратах), было бы трудно усвоить ошибку. Кроме того, при создании экземпляра класса могут возникать побочные эффекты времени выполнения.

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

Уверенный в себе угол, но плохие вещи могут произойти, если вы не требуете от людей доступа к функциям constexpr.

Ответ 2

Предотвращение клиентского кода, ожидающего большего, чем вы обещаете

Скажем, я пишу библиотеку и имею функцию, которая в настоящее время возвращает константу:

awesome_lib.h:

inline int f() { return 4; }

Если constexpr не требуется, вы - как автор кода клиента - можете уйти и сделать что-то вроде этого:

client_app.cpp:

#include <awesome_lib.h>
int my_array[f()];

Затем я должен изменить f(), чтобы сказать, вернуть значение из файла конфигурации, ваш код клиента сломается, но я бы не знал, что я рискнул бы сломать ваш код. В самом деле, может быть, только когда у вас возникнут проблемы с производством и перекомпиляция, что вы найдете эту дополнительную проблему, которая разочаровывает вашу перестройку.

Изменив только реализацию f(), я бы эффективно изменил использование, которое может быть сделано в интерфейсе.

Вместо этого С++ 11 предоставляет constexpr, поэтому я могу обозначить, что клиентский код может иметь разумное ожидание функции, остающейся a constexpr, и использовать ее как таковую. Я знаю и одобряю такое использование как часть моего интерфейса. Как и в С++ 03, компилятор продолжает гарантировать, что клиентский код не построен, чтобы зависеть от других функций не constexpr, чтобы предотвратить описанный выше сценарий "нежелательной/неизвестной зависимости"; это больше, чем документация - это компиляция времени.

Следует отметить, что это продолжает тенденцию С++, предлагающую лучшие альтернативы традиционным потребностям макросов препроцессора (рассмотрите #define F 4 и как клиентский программист знает, считает ли программист lib честной игрой, чтобы сказать #define F config["f"]), с их хорошо известными "золками", такими как находящиеся вне системы определения пространства имен/классов языка.

Почему не существует диагностики для "явно" функций без констант?

Я думаю, что путаница здесь объясняется тем, что constexpr не упреждающим образом гарантируется, что существует какой-либо набор аргументов, для которых результат является фактически compile-time const: скорее, это требует от программиста взять на себя ответственность за это (в противном случае §7.1. 5/5 в Стандарте считает, что программа плохо сформирована, но не требует, чтобы компилятор выдал диагностику). Да, это несчастливо, но не удаляет вышеупомянутую утилиту constexpr.

Итак, возможно, вопрос не должен быть "какой точкой constexpr", но "почему я могу скомпилировать функцию constexpr, которая никогда не сможет вернуть значение const?". Ответ: потому что возникнет необходимость в исчерпывающем анализе ветвей, который может включать любое количество комбинаций. Это может быть чрезмерно дорогостоящим во время компиляции и/или памяти - даже за пределами возможностей любого воображаемого оборудования - для диагностики. Кроме того, даже когда это практично, нужно точно диагностировать такие случаи, это совершенно новая возможность червей для писателей-компиляторов (которые уже достаточно заняты реализацией С++ 11). Также были бы последствия для программы, такие как определение функций, вызываемых из функции constexpr, которая должна быть видимой при выполнении проверки (и функции, вызывающие вызовы и т.д.).

Между тем отсутствие constexpr продолжает запрещать использование в качестве значения const: строгость находится на стороне sans-constexpr. Это полезно, как показано выше.

Сравнение с функциями, отличными от `const`

  • constexpr предотвращает int x[f()], в то время как отсутствие const предотвращает const X x; x.f(); - они оба обеспечивают, чтобы клиентский код не фиксировал нежелательную зависимость

  • в обоих случаях вы не хотите, чтобы компилятор автоматически определял const[expr] -ness:

    • вы не хотите, чтобы клиентский код вызывал функцию-член в объекте const, когда вы уже можете ожидать, что эта функция будет развиваться, чтобы изменить наблюдаемое значение, нарушая клиентский код

    • вам не понадобится значение, используемое в качестве параметра шаблона или размера массива, если вы уже предполагали, что оно будет определено позже во время выполнения

  • они отличаются тем, что компилятор применяет const использование других членов в функции члена const, но не обеспечивает принудительный результат постоянной компиляции с помощью constexpr ( из-за практических ограничений компилятора)

Ответ 3

Без ключевого слова компилятор не может диагностировать ошибки. Компилятор не сможет сказать вам, что функция является синтаксически недействительной как constexpr. Хотя вы сказали, что это обеспечивает "ложное чувство безопасности", я считаю, что лучше подобрать эти ошибки как можно раньше.

Ответ 4

Мы можем жить без constexpr, но в некоторых случаях это делает код проще и интуитивно понятным.
Например, у нас есть класс, который объявляет массив с некоторой эталонной длиной:

template<typename T, size_t SIZE>
struct MyArray
{
  T a[SIZE];
};

Обычно вы можете объявить MyArray как:

int a1[100];
MyArray<decltype(*a1), sizeof(a1)/sizeof(decltype(a1[0]))> obj;

Теперь посмотрим, как это происходит с constexpr:

template<typename T, size_t SIZE>
constexpr
size_t getSize (const T (&a)[SIZE]) { return SIZE; }

int a1[100];
MyArray<decltype(*a1), getSize(a1)> obj;

Короче говоря, любая функция (например, getSize(a1)) может использоваться как аргумент шаблона только в том случае, если компилятор распознает ее как constexpr.

constexpr также используется для проверки отрицательной логики. Это гарантирует, что данный объект находится во время компиляции. Вот ссылка ссылка, например.

int i = 5;
const int j = i; // ok, but `j` is not at compile time
constexprt int k = i; // error