La variable statique constexpr à l'intérieur d'une fonction a-t-elle un sens?

193

Si j'ai une variable à l'intérieur d'une fonction (par exemple, un grand tableau), est-il logique de la déclarer à la fois staticet constexpr? constexprgarantit que le tableau est créé au moment de la compilation, serait- staticil inutile?

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

Y a-t-il staticvraiment quelque chose à faire en termes de code généré ou de sémantique?

David Stone
la source

Réponses:

231

La réponse courte est que non seulement elle est staticutile, mais elle sera toujours souhaitée.

Tout d'abord, notez que staticet constexprsont complètement indépendants les uns des autres. staticdéfinit la durée de vie de l'objet pendant l'exécution; constexprspécifie que l'objet doit être disponible lors de la compilation. La compilation et l'exécution sont disjointes et non contiguës, à la fois dans le temps et dans l'espace. Donc, une fois le programme compilé, ce constexprn'est plus pertinent.

Chaque variable déclarée constexprest implicitement constmais constet staticest presque orthogonale (sauf pour l'interaction avec les static constentiers.)

Le C++modèle objet (§1.9) exige que tous les objets autres que les champs de bits occupent au moins un octet de mémoire et aient des adresses; en outre, tous ces objets observables dans un programme à un moment donné doivent avoir des adresses distinctes (paragraphe 6). Cela n'oblige pas tout à fait le compilateur à créer un nouveau tableau sur la pile pour chaque invocation d'une fonction avec un tableau const non statique local, car le compilateur pourrait se réfugier dans le as-ifprincipe à condition qu'il puisse prouver qu'aucun autre objet de ce type ne peut être observé.

Cela ne sera malheureusement pas facile à prouver, à moins que la fonction ne soit triviale (par exemple, elle n'appelle aucune autre fonction dont le corps n'est pas visible dans l'unité de traduction) car les tableaux, plus ou moins par définition, sont des adresses. Ainsi, dans la plupart des cas, le const(expr)tableau non statique devra être recréé sur la pile à chaque invocation, ce qui va à l'encontre du point de pouvoir le calculer au moment de la compilation.

D'un autre côté, un static constobjet local est partagé par tous les observateurs et peut en outre être initialisé même si la fonction dans laquelle il est défini n'est jamais appelée. Donc rien de ce qui précède ne s'applique, et un compilateur est libre non seulement de n'en générer qu'une seule instance; il est libre d'en générer une seule instance dans un stockage en lecture seule.

Vous devriez donc certainement utiliser static constexprdans votre exemple.

Cependant, il y a un cas où vous ne voudriez pas utiliser static constexpr. À moins qu'un constexprobjet déclaré ne soit utilisé ou déclaré par ODRstatic , le compilateur est libre de ne pas l'inclure du tout. C'est assez utile, car cela permet d'utiliser des constexprtableaux temporaires au moment de la compilation sans polluer le programme compilé avec des octets inutiles. Dans ce cas, vous ne voudriez clairement pas utiliser static, car il staticest susceptible de forcer l'objet à exister au moment de l'exécution.

rici
la source
2
@AndrewLazarus, vous ne pouvez pas rejeter constun constobjet, uniquement à partir de const X*qui pointe vers un X. Mais ce n'est pas le but; le fait est que les objets automatiques ne peuvent pas avoir d'adresses statiques. Comme je l'ai dit, constexprcesse d'avoir un sens une fois la compilation terminée, il n'y a donc rien à jeter (et peut-être rien du tout, car il n'est même pas garanti que l'objet existe au moment de l'exécution.)
rici
17
J'ai l'impression que non seulement cette réponse est incroyablement déroutante, mais aussi contradictoire. Par exemple, vous dites que vous voulez presque toujours staticet constexprmais expliquez qu'ils sont orthogonaux et indépendants, faisant des choses différentes. Vous mentionnez ensuite une raison pour ne PAS combiner les deux car cela ignorerait l'utilisation de l'ODR (ce qui semble utile). Oh et je ne vois toujours pas pourquoi static devrait être utilisé avec constexpr puisque static est pour les choses d'exécution. Vous n'avez jamais expliqué pourquoi statique avec constexpr est important.
void.pointer
2
@ void.pointer: Vous avez raison sur le dernier paragraphe. J'ai changé l'intro. Je pensais avoir expliqué l'importance de static constexpr(cela empêche le tableau constant de devoir être recréé à chaque appel de fonction), mais j'ai modifié quelques mots qui pourraient le rendre plus clair. Merci.
rici
8
Peut également être utile de mentionner les constantes de temps de compilation par rapport aux constantes d'exécution. En d'autres termes, si une constexprvariable constante n'est utilisée que dans des contextes de compilation et n'est jamais nécessaire au moment de l'exécution, staticcela n'a aucun sens, car au moment où vous arrivez à l'exécution, la valeur a été effectivement "intégrée". Cependant, si constexprest utilisé dans des contextes d'exécution (en d'autres termes, le constexprdevrait être converti en constimplicitement, et disponible avec une adresse physique pour le code d'exécution), il voudra staticassurer la conformité ODR, etc. C'est ce que je comprends, au moins.
void.pointer
3
Un exemple pour mon dernier commentaire: static constexpr int foo = 100;. Il n'y a aucune raison pour que le compilateur ne puisse pas substituer l'usage de foopartout pour littéral 100, à moins que le code ne fasse quelque chose comme &foo. Donc staticon foon'a aucune utilité dans ce cas puisqu'il foon'existe pas à l'exécution. Encore une fois, tout dépend du compilateur.
void.pointer
10

En plus de la réponse donnée, il convient de noter que le compilateur n'est pas obligé d'initialiser la constexprvariable au moment de la compilation, sachant que la différence entre constexpret static constexprest que pour l'utiliser, static constexprvous vous assurez que la variable n'est initialisée qu'une seule fois.

Le code suivant montre comment la constexprvariable est initialisée plusieurs fois (avec la même valeur cependant), alors qu'elle static constexprn'est sûrement initialisée qu'une seule fois.

De plus, le code compare l'avantage de constexprcontre consten combinaison avec 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;
}

Sortie de programme possible:

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

Comme vous pouvez le voir, vous êtes constexprinitialisé plusieurs fois (l'adresse n'est pas la même) tandis que le staticmot clé garantit que l'initialisation n'est effectuée qu'une seule fois.

metablaster
la source
ne pouvons-nous pas utiliser constexpr const short constexpr_shortpour donner une erreur si constexpr_short est à nouveau initialisé
akhileshzmishra
votre syntaxe de constexpr constn'a aucun sens car constexprdéjà const, l'ajout constune ou plusieurs fois est ignoré par le compilateur. Vous essayez d'attraper une erreur mais ce n'est pas une erreur, c'est ainsi que fonctionnent la plupart des compilateurs.
metablaster