Верны ли мои рассуждения, или я упускаю какую-то проблему, которую могут вызвать переменные stati c constexpr? duration необходимо учитывать при работе с constexpr
переменными, если им разрешена c -продолжительность хранения внутри контекста constexpr
.
Объекты со stati c продолжительностью хранения только в функции получают построен на первой записи в функцию . Обычно в это время к константам (для констант времени выполнения) применяется резервное копирование. Если static constexpr
было разрешено в контексте constexpr
, при генерации во время компиляции должно произойти одно из двух:
- Выполнение функции во время компиляции теперь должно генерировать резервное копирование для констант stati c в случае использования ODR - даже если он никогда не используется во время выполнения (что было бы ненулевыми накладными расходами), или
- Выполнение функции во время компиляции теперь должно временно создать константу, экземпляр которой будет создаваться при каждом вызове, и, наконец, предоставить хранилище, когда ветвь вызывает ее с контекстом времени выполнения (независимо от того, сгенерирована она во время компиляции или нет). Это нарушило бы существующие правила для объектов с длительностью хранения c.
Поскольку constexpr
по своей природе не имеет состояния во всем контексте, применение объекта хранения c стат во время функции constexpr
вызов внезапно добавляет состояние между constexpr
вызовами - что является большим изменением для текущих правил constexpr
. Хотя функции constexpr
могут изменять локальное состояние, это состояние не затрагивается глобально.
C ++ 20 также ослабляет требования constexpr
, позволяя деструкторам быть constexpr, что вызывает больше вопросов, например, когда деструктор должен выполняться в указанных выше случаях.
Я не говорю, что это не решаемая проблема ; просто существующие языковые средства делают решение этой проблемы немного сложным, не нарушая определенных правил. в определенный момент времени.
Есть ли какие-либо предложения, чтобы исправить это?
Мне ничего не известно. В различных группах Google были обсуждения правил этого, но я не видел никаких предложений по этому поводу. Если кто-то знает что-либо, укажите его в комментариях, и я обновлю свой ответ.
Обходные пути
Есть несколько способов избежать этого ограничения в зависимости от того, какой API вам нужен, и каковы требования:
- Бросить константу в область видимости файла, возможно, в пространстве имен
detail
. Это делает вашу константу глобальной, которая может работать или не работать в соответствии с вашими требованиями. - Вставьте константу в константу
static
в struct
/ class
. Это можно использовать, если данные должны быть шаблонными, и позволяет использовать private
и friend
ship для управления доступом к этой константе. - Сделайте функцию функцией
static
на struct
/ class
, который содержит данные (если это соответствует вашим требованиям).
Все три подхода работают хорошо, если данные должны быть шаблоном, хотя подход 1 будет работать только с C ++ 14 (в C ++ 11 не было шаблонов переменных), тогда как 2 и 3 можно использовать в C ++ 11.
Самым чистым решением с точки зрения инкапсуляции, на мой взгляд, было бы третий способ перемещения данных и действующей функции (функций) в struct
или class
. Это сохраняет данные, тесно связанные с функциональностью. Например:
class foo_util
{
public:
static constexpr int foo(int i); // calls at(v, i);
private:
static constexpr std::array<int, 100> v = { ... };
};
Ссылка на обозреватель компилятора
Это сгенерирует сборки, идентичные вашему подходу foo1
, но при этом будет constexpr
.
Если бросание функции в class
или struct
невозможно для ваших требований (возможно, это должна быть бесплатная функция?), То вы застряли либо при перемещении данных в область видимости файла (возможно, защищен соглашением о пространстве имен detail
), или бросая его в непересекающийся struct
или class
, который обрабатывает данные. Последний подход может использовать модификаторы доступа и дружбу для управления доступом к данным. Это решение может работать, хотя оно, по общему признанию, не такое чистое:
#include <array>
constexpr int at(const std::array<int, 100>& v, int index)
{
return v[index];
}
constexpr int foo(int i);
namespace detail {
class foo_holder
{
private:
static constexpr std::array<int, 100> v = {
5, 7, 0, 0, 5 // The rest are zero
};
friend constexpr int ::foo(int i);
};
} // namespace detail
constexpr int foo(int i) {
return at(detail::foo_holder::v, i);
}
Ссылка на обозреватель компилятора .
Это, опять же, производит идентичную сборку foo1
, в то время как все еще позволяя ему быть constexpr
.