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