https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96996
Bug ID: 96996 Summary: Missed optimzation for constant members of non-constant objects Product: gcc Version: 10.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: web Assignee: unassigned at gcc dot gnu.org Reporter: matthijs at stdin dot nl Target Milestone: --- When a global class instance is const and initialized using constant arguments to a constexpr constructor, any member references are optimized away (using the constant value rather than a lookup). However, when the object is *not* const, but does have const members, such optimization does not happen. $ cat test2.cpp; gcc -O3 -S -Wall -Wextra -fdump-tree-optimized=/dev/stdout test2.cpp constexpr int v = 1; struct Test { constexpr Test(int v, const int *p) : v(v), p(p) { } int const v; const int * const p; }; const Test constant_test(v, &v); Test non_constant_test(v, &v); int constant_ref() { return constant_test.v + *constant_test.p; } int non_constant_ref() { return non_constant_test.v + *non_constant_test.p; } ;; Function constant_ref (_Z12constant_refv, funcdef_no=3, decl_uid=2360, cgraph_uid=4, symbol_order=6) constant_ref () { <bb 2> [local count: 1073741824]: return 2; } ;; Function non_constant_ref (_Z16non_constant_refv, funcdef_no=4, decl_uid=2362, cgraph_uid=5, symbol_order=7) non_constant_ref () { int _1; const int * _2; int _3; int _5; <bb 2> [local count: 1073741824]: _1 = non_constant_test.v; _2 = non_constant_test.p; _3 = *_2; _5 = _1 + _3; return _5; } In the constant_f() case, the values are completely optimized and the return value is determined at compiletime. In the non_constant_f() case, the values are retrieved at runtime. However, AFAICS there should be no way that these values can be modified at runtime, even when the object itself is not const, since the members are const. So AFAICS, it shoul be possible to evaluation non_constant_f() at compiletime as well. Looking at the C++ spec (I'm quoting from the C++14 draft here), this would seem to be possible as well. [basic.type.qualifier] says "A const object is an object of type const T or a non-mutable subobject of such an object." If I read [intro.object] correctly, subobjects (such as non-static member variables) are also objects, so a non-static member variable declared const would be a "const object". [dcl.type.cv] says "Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const object during its lifetime (3.8) results in undefined behavior." So, one can assume that the const member variable is not modified, because if it was, that would be undefined behavior. There is still the caveat of "during its lifetime", IOW, what if you would destroy the object and create a new one it is place. However, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80794#c5 for a discussion of this case. In short, replacing non_constant_test with a new object is possible, but only when it "does not contain any non-static data member whose type is const-qualified or a reference type", which does not hold for this object. This sounds like this provision was made for pretty much this case, even. I suspect that reason that it works for the const object now, is because of the rules for constant expressions. [expr.const] defines rules for constant exprssions and seems to only allow using(through lvalue-to-rvalue conversion) objects of non-integral types when they are constexpr. I can imagine that gcc derives that constant_test might be effectively constexpr, making any expressions that use it also effectively constant expressions. This same derivation probably does not happen for subobjects (I guess "constexpr" is not actually a concept that applies to subobjects at all). However, I think this does not mean this optimization would be invalid, just that it would happen on different grounds than the current optimization. This issue is also related to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80794, but AFAICS that is more about partial compilation and making assumptions about what an external function can or cannot do, while this issue is primarily about link-time (though maybe they are more similar internally, I don't exactly know). I believe that this optimization would be quite significant to make, since it allows better abstraction and separation of concerns (i.e. it allows writing a class to be generic, using constructor-supplied parameters, but if you pass constants for these parameters and have just a single instance of such a class, or when methods are inlined or constprop'd, there could be zero runtime overhead for this extra abstraction). Currently, I believe that you either have to accept runtime overhead, or resort to using template argument for such parameters (which significantly complicates everything, and takes away freedom from the compiler to decide when to resolve things at compiletime or runtime).