Имеет ли смысл статическая переменная constexpr внутри функции?

193

Если у меня есть переменная внутри функции (скажем, большой массив), имеет ли смысл объявлять ее одновременно staticи constexpr? constexprгарантирует, что массив создается во время компиляции, так что staticбудет бесполезно?

void f() {
    static constexpr int x [] = {
        // a few thousand elements
    };
    // do something with the array
}

Делает ли что-то на staticсамом деле с точки зрения сгенерированного кода или семантики?

Дэвид Стоун
источник

Ответы:

231

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

Во-первых, обратите внимание, что staticи constexprполностью независимы друг от друга. staticопределяет время жизни объекта во время выполнения; constexprуказывает, что объект должен быть доступен во время компиляции. Компиляция и исполнение являются несвязанными и несмежными как во времени, так и в пространстве. Поэтому, как только программа скомпилирована, constexprона больше не актуальна.

Каждая объявленная переменная constexprнеявно, constно constи staticпочти ортогональна (за исключением взаимодействия с static constцелыми числами.)

C++Объектная модель (§1.9) требует , чтобы все объекты, кроме битовых полей занимают по меньшей мере , один байты памяти и имеют адреса; кроме того, все такие объекты, наблюдаемые в программе в данный момент, должны иметь разные адреса (пункт 6). Это совсем не требует, чтобы компилятор создавал новый массив в стеке для каждого вызова функции с локальным нестатическим константным массивом, потому что компилятор мог бы укрыться в as-ifпринципе, при условии, что он может доказать, что никакой другой такой объект не может быть наблюдаемый.

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

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

Так что вы обязательно должны использовать static constexprв своем примере.

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

RICi
источник
2
@AndrewLazarus, вы не можете откинуть constот constобъекта, только из const X*которых указывает на X. Но дело не в этом; Дело в том, что автоматические объекты не могут иметь статические адреса. Как я уже говорил, constexprперестает быть значимым , как только компиляция закончится, так что нет ничего разбрасывать (и , вполне возможно , вообще ничего, так как объект не гарантируется даже существовать во время выполнения.)
RICi
17
Я чувствую, что этот ответ не только невероятно запутанный, но и противоречивый. Например , вы говорите , что вы почти всегда хотите staticи constexprно объяснить , что они ортогональны и независимы, делать разные вещи. Затем вы упоминаете причину, по которой НЕ объединять эти два параметра, так как это будет игнорировать использование ODR (что кажется полезным). Да, и я до сих пор не понимаю, почему static следует использовать с constexpr, так как static предназначен для работы во время выполнения. Вы никогда не объясняли, почему важно использовать static с constexpr.
void.pointer
2
@ void.pointer: Вы правы насчет последнего абзаца. Я изменил вступление. Я думал, что объяснил важность static constexpr(это предотвращает повторное создание константного массива при каждом вызове функции), но я подправил некоторые слова, которые могут сделать его более понятным. Спасибо.
Ричи
8
Также может быть полезно упомянуть константы времени компиляции против констант времени выполнения. Другими словами, если constexprпостоянная переменная используется только в контекстах времени компиляции и никогда не нужна во время выполнения, то staticэто не имеет смысла, поскольку к моменту, когда вы добираетесь до времени выполнения, значение эффективно «встроено». Тем не менее, если constexprон используется в контекстах времени выполнения (другими словами, constexprон должен быть constнеявно преобразован и доступен с физическим адресом для кода времени выполнения), он захочет staticобеспечить соответствие ODR и т. Д. Это, по крайней мере, мое понимание.
void.pointer
3
Пример моего последнего комментария: static constexpr int foo = 100;. Нет никаких причин, по которым компилятор не мог бы заменить использование fooвезде буквальным 100, если код не делал что-то подобное &foo. Так что staticна fooнет полезность в этом случае , так как fooне существует во время выполнения. Опять все до компилятора.
void.pointer
10

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

Следующий код демонстрирует, как constexprпеременная инициализируется несколько раз (хотя с одним и тем же значением), в то время static constexprкак, безусловно, инициализируется только один раз.

Кроме того, код сравнивает преимущество constexprпротив constв сочетании с static.

#include <iostream>
#include <string>
#include <cassert>
#include <sstream>

const short const_short = 0;
constexpr short constexpr_short = 0;

// print only last 3 address value numbers
const short addr_offset = 3;

// This function will print name, value and address for given parameter
void print_properties(std::string ref_name, const short* param, short offset)
{
    // determine initial size of strings
    std::string title = "value \\ address of ";
    const size_t ref_size = ref_name.size();
    const size_t title_size = title.size();
    assert(title_size > ref_size);

    // create title (resize)
    title.append(ref_name);
    title.append(" is ");
    title.append(title_size - ref_size, ' ');

    // extract last 'offset' values from address
    std::stringstream addr;
    addr << param;
    const std::string addr_str = addr.str();
    const size_t addr_size = addr_str.size();
    assert(addr_size - offset > 0);

    // print title / ref value / address at offset
    std::cout << title << *param << " " << addr_str.substr(addr_size - offset) << std::endl;
}

// here we test initialization of const variable (runtime)
void const_value(const short counter)
{
    static short temp = const_short;
    const short const_var = ++temp;
    print_properties("const", &const_var, addr_offset);

    if (counter)
        const_value(counter - 1);
}

// here we test initialization of static variable (runtime)
void static_value(const short counter)
{
    static short temp = const_short;
    static short static_var = ++temp;
    print_properties("static", &static_var, addr_offset);

    if (counter)
        static_value(counter - 1);
}

// here we test initialization of static const variable (runtime)
void static_const_value(const short counter)
{
    static short temp = const_short;
    static const short static_var = ++temp;
    print_properties("static const", &static_var, addr_offset);

    if (counter)
        static_const_value(counter - 1);
}

// here we test initialization of constexpr variable (compile time)
void constexpr_value(const short counter)
{
    constexpr short constexpr_var = constexpr_short;
    print_properties("constexpr", &constexpr_var, addr_offset);

    if (counter)
        constexpr_value(counter - 1);
}

// here we test initialization of static constexpr variable (compile time)
void static_constexpr_value(const short counter)
{
    static constexpr short static_constexpr_var = constexpr_short;
    print_properties("static constexpr", &static_constexpr_var, addr_offset);

    if (counter)
        static_constexpr_value(counter - 1);
}

// final test call this method from main()
void test_static_const()
{
    constexpr short counter = 2;

    const_value(counter);
    std::cout << std::endl;

    static_value(counter);
    std::cout << std::endl;

    static_const_value(counter);
    std::cout << std::endl;

    constexpr_value(counter);
    std::cout << std::endl;

    static_constexpr_value(counter);
    std::cout << std::endl;
}

Возможный вывод программы:

value \ address of const is               1 564
value \ address of const is               2 3D4
value \ address of const is               3 244

value \ address of static is              1 C58
value \ address of static is              1 C58
value \ address of static is              1 C58

value \ address of static const is        1 C64
value \ address of static const is        1 C64
value \ address of static const is        1 C64

value \ address of constexpr is           0 564
value \ address of constexpr is           0 3D4
value \ address of constexpr is           0 244

value \ address of static constexpr is    0 EA0
value \ address of static constexpr is    0 EA0
value \ address of static constexpr is    0 EA0

Как видите, constexprинициализация выполняется несколько раз (адрес не совпадает), а staticключевое слово гарантирует, что инициализация выполняется только один раз.

metablaster
источник
мы не можем использовать constexpr const short constexpr_shortдля выдачи ошибки, если constexpr_short снова инициализируется
akhileshzmishra
ваш синтаксис constexpr constне имеет смысла, потому что constexprуже есть const, добавление constодин или несколько раз игнорируется компилятором. Вы пытаетесь поймать ошибку, но это не ошибка, так работает большинство компиляторов.
метабластер