переменные stati c constexpr в функции constexpr - PullRequest
9 голосов
/ 18 июня 2020
Переменные

static не допускаются внутри функции constexpr. Это имеет смысл, поскольку static вводит состояние в предположительно чистую функцию.

Однако я не понимаю, почему мы не можем иметь переменную static constexpr в функции constexpr. Гарантируется, что всегда будет одно и то же значение, поэтому функция останется чистой.

Зачем мне это? Потому что static имеет значение во время выполнения. Рассмотрим этот код:

#include <array>

constexpr int at(const std::array<int, 100>& v, int index)
{
    return v[index];
}

int foo1(int i) {
    static constexpr std::array<int, 100> v = { 
        5, 7, 0, 0, 5  // The rest are zero
    };
    return at(v, i);
}

constexpr int foo2(int i) {
    constexpr std::array<int, 100> v = { 
        5, 7, 0, 0, 5  // The rest are zero
    };
    return at(v, i);
}

int foo2_caller(int i) {
    return foo2(i);
}

Live: https://gcc.godbolt.org/z/umdXgv

foo1 имеет 3 инструкции asm, поскольку он хранит буфер в хранилище stati c . В то время как foo2 имеет 15 asm-инструкций, потому что требуется выделять и инициализировать буфер при каждом вызове, и компилятор не смог оптимизировать это.

Обратите внимание, что foo1 здесь только для того, чтобы показать недостаток в foo2. Я хочу написать функцию, которую я смогу использовать как во время компиляции, так и во время выполнения. Это идея foo2. Но мы видим, что он не может быть таким же эффективным, как foo1 только во время выполнения, что беспокоит.

Единственное значимое обсуждение, которое я нашел , - это , но оно не обсуждает static constexpr в частности.

Вопросы:

  • Правильно ли я рассуждаю, или я упускаю какую-то проблему, которую могут вызвать static constexpr переменные?
  • Есть ли какие-нибудь предложения по исправлению?

Ответы [ 2 ]

8 голосов
/ 18 июня 2020

Верны ли мои рассуждения, или я упускаю какую-то проблему, которую могут вызвать переменные 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 вам нужен, и каковы требования:

  1. Бросить константу в область видимости файла, возможно, в пространстве имен detail. Это делает вашу константу глобальной, которая может работать или не работать в соответствии с вашими требованиями.
  2. Вставьте константу в константу static в struct / class. Это можно использовать, если данные должны быть шаблонными, и позволяет использовать private и friend ship для управления доступом к этой константе.
  3. Сделайте функцию функцией 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.

2 голосов
/ 18 июня 2020

Как это? Я вставил массив в не типовой параметр шаблона:

template<std::array<int, 100> v = {5, 7, 0, 0, 5}>
constexpr int foo2(int i) {
    return at(v, i);
}

На godbolt , разборка foo2 теперь совпадает с разборкой вашего foo1. В настоящее время это работает на G CC, но не clang; кажется, что clang здесь стоит за стандартом C ++ 20 (см. этот вопрос SO).

...