Dear GCC-developer team, The specification of the const-attribute is a bit ambiguous, it does not fully specify which global variables are allowed to be read from. Obviously constant global compile-time initialized variables can be accessed without problem. But what about run time initialized variables like a lookup table or the initialization of an api? Does GCC may invoke the function before the initialization of the lookup table/api; or is it guaranteed, that the function is only called in places, where it would be called without the attribute. I also propose some additional attributes to let the programmer clarify his/her intentions.
Here are some code snippets, how GCC might change the generated code, according to the documentation. In my opinion, each generated output should be assigned to a distinct attribute. *Function declarations:* *void init_values();* *int read_value(int index) [[gnu::const]];* *void use_value(int value);* *Problematic code:* *int main(int argc, char** argv) {* * init_values();* *for(int i=1; i < argc; ++i) {* * int value = read_value(0); // constant* * use_value(value, argv[i]);* * }* *}* Here are some possible outputs, which are possible, because of the ambiguity of the documentation. The attribute next to "Transformation" is the proposed new attribute names, which could lead to the transformatio (when [[gnu::const]] is replaced with it). *Transformation [[gnu::const, gnu::is_simple]]* *int read_value__with_0;* *int main(int argc, char**) {* * read_value__with_0 = read_value(0);* * init_values();* *for(int i=1; i < argc; ++i) {* * use_value(read_value__with_0, argv[i]);* * }* *}* The code is called at some undefined point, maybe at the start of main, even if it is never used, because the compiler assumes, that the function simply reads from constant memory/does a simple calculation. In the case of the example, it is not what the programmer intended. *Transformation [[gnu::const]]* *int read_value__with_0;* *int main(int argc, char** argv) {* * if(argc > 1) read_value__with_0 = read_value(0);* * init_values();* * for(int i=1; i < argc; ++i) {* * use_value(read_value__with_0, argv[i]);* * }* *}* This is almost the same to the previous version, but it only calls the function, if the result is used. Both attributes (the time when it is called is undefined) can also result in the following code where the attributes give a stronger guarantee, when it is called (the first time). The following works semantically as the programmer intended/when the attributes were ignored. *Transformation** [[gnu::const(init_values()), gnu::is_simple]]* *int read_value__with_0;* *int main() {* * init_values();* * read_value__with_0 = read_value(0);* *for(int i=1; i < 100; ++i) {* * use_value(read_value__with_0, argv[i]);* * }* *}* *Transformation **[[gnu::const(init_values())]]* *int read_value__with_0;* *int main() {* * init_values();* * if(argc > 1) read_value__with_0 = read_value(0);* *for(int i=1; i < 100; ++i) {* * use_value(read_value__with_0, argv[i]);* * }* *}* The function is guaranteed to never use the returned values again, when the given expression in the attribute parameter list is called, so the compiler interprets the given function as an initializing function. *PROPOSED ATTRIBUTES* *[[gnu::is_simple]]* In connection to the [[gnu::const]] attribute this attribute implies, that the function might be called when the result is not used. Eg. when the function is called in a conditional branch, the call can be extracted from the branch and can be called outside. This should be applied to very simple functions. *[[gnu::const]]* The meaning is kept as before. But the function can be called any time. This enshures, that no expression is called inside the function, which has an observable effect. But when the function is used in a [[gnu::const(...)]] annotated function each call to this function is considered an observable effect to the function. *[[gnu::const([(<parameter list>):]<expression>,...)]]* This is an extension to the old [[gnu::const]] attribute. But: When some of the expressions is called, the previous return values are now invalid (the function depends on the expression). The parameter list specifies variables used in the expression, which are not known. In the expression, the parameter of the annotated function itself, member variables/functions and static variables/functions can be used. If [[gnu::const]] is used on non static member functions, the function automaticly depends on the constructor. Destructors automaticly depend on all non static member functions. Unclear: A function should never depend on a function/expression, which calls the annotated function. *[[gnu::calls([(<parameter list>):]<expression>,...)]]* This function attributes tells the compiler, that the given expression might be called inside. This makes older values recieved from [[gnu::const(...)]] functions (which depend on (parts of) the expression) dirty when called. The attribute is inherited to parent functions, which call the annotated function (only if the implementation is known, the compiler should suggest this for the functions). The functions called in the expression itself do not need to be implemented/linked. The parameter list specifies variables used in the expression, which are not known. In the expression, the parameter of the annotated functions, member variables/functions and global variables/functions can be used. The compiler should suggest this attribute, when it sees an expression inside the body, which is referenced by [[gnu::const(...)]]. *Benefits* *class map {* * //...* *public:* * void set(int key, int value) [[gnu::const]];* * void get(int key) [[gnu::const((int value): this->set(key, value))]];* *};* The function *get* is annotated with the proposed new extension of the *const* attribute, the attribute specifies, that the return value is constant, until the *set* member function is called with the same *key* value. The *set* function does not change any state, which can be observed by any functions. Except *get* (or the destructor), which directly depends on this function. This also implies, that all calls except the last to this function can be omitted, before *get* is called. The function only depends on the constructor (automaticly). class counter { //... public: void increase() [[gnu::const(this->increase(), this->reset())]]; void reset() [[gnu::const]] int get() [[gnu::const(this->increase(), this->reset())]]; }; When referencing to itself with the [[gnu::const(...)] attribute, repeating calls cannot be omitted. It is important, that sideeffects violating the gurantee given by the attributes should not cause undefined behavior (when debugging), the compiled programm should simply invoke none or the whole sideeffects; this is important for logging/debugging, which sould be possible inside such functions. Maybe it can be undefined for release builds. (In release mode) This would be undefined behavior, but a valid optimization, if for example *glob* is freed in *foo* and *int foo() [[const]];* *//...* *char* ptr = glob;* *int y = foo();* *ptr = glob;* *puts(ptr);* is transformed to *char* ptr = glob;* *int y = foo();* *puts(ptr);* In debugmode, changes like the change of *glob* should be allowed in foo, so that output functions work. Maybe restrict the changes only on output functions. On questions, you can write to me back. Sincerely, cmdLP