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

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

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

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

Ответ 1

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

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

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

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

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

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

Поэтому вам обязательно следует использовать static constexpr в вашем примере.

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

Ответ 2

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