This question already has answers here:
What exactly is the "as-if" rule?
(3 answers)
Closed 2 years ago.
Lets have following code in C++14:
using namespace std;
void foo(int a)
{
cout << a;
}
int main()
{
//version1
foo(13);
//version2
static constexpr int tmp = 13;
foo(tmp);
return 0;
}
Will the compiler automatically optimize version2 to version1 so that static constexpr variable will be inlined (something like defines are handled during processor in C)? If so, what are the rules for this? Will it be still inlined if it would be only constexpr or static const ?
From http://eel.is/c++draft/dcl.constexpr#1.sentence-3
A function or static data member declared with the constexpr or consteval specifier is implicitly an inline function or variable.
Compilers are given wide latitude under the "as if" rule to optimize as they see fit. Given the fairly trivial nature of the code you posted, a compiler is just as capable of optimizing "version 2" even if you removed static constexpr from the definition. The compiler can see that you don't change the value of the variable between its initialization and its use, and it can see that the use is a compile-time value, so it can call that function with the initialization value. And even optimize the function itself away if it can see the definition.
Whether any particular compiler will or won't optimize away some code depends on compiler settings and the particular details of the code (specifically, code that intervenes between the value's creation and its use). There are no "rules" for this.
Related
Take note of the following C++ code:
#include <iostream>
using std::cout;
int foo (const int);
int main ()
{
cout << foo(3);
}
int foo (int a)
{
a++;
return a;
}
Notice that the prototype of foo() takes a const int and that the definition takes an int. This compile without any errors...
Why are there no compilation errors?
Because it doesn't matter to the caller of the foo function whether foo modifies its copy of the variable or not.
Specifically in the C++03 standard, the following 2 snippets explain exactly why:
C++03 Section: 13.2-1
Two function declarations of the same name refer to the same function if they are in the same scope and
have equivalent parameter declarations (13.1).
C++03 Section: 13.1-3
Parameter declarations that differ only in the presence or absence of const and/or volatile are equivalent. Only the const and volatile type-specifiers at the outermost level of the parameter type specification are ignored in this fashion; const and volatile type-specifiers buried within a parameter type specification are significant and can be used to distinguish overloaded function declarations.
Top-level const (i.e., that applies to the value that's passed, not something to which it points or refers) affects only the implementation, not the interface, of a function. The compiler ignores it from the interface viewpoint (i.e., the calling side) and enforces it only on the implementation (i.e., code in the body of the function).
As others have explained, the Standard says it's ok, and that the compiler can afford to be lenient about enforcing this because it doesn't affect the caller, but nobody's answered why the compiler should choose to be lenient. It's not particularly lenient in general, and a programmer who's just been looking at the interface then dives into the implementation may have it in the back of their mind that a parameter is const when it's not or vice versa - not a good thing.
This leniency allows implementation changes without modifying headers, which using traditional make tools triggers recompilation of client code. This can be a serious issue in enterprise scale development, where a non-substantive change in a low-level header (e.g. logging) can force rebuilding of virtually all objects between it and the applications... wasting thousands of hours of CPU time and delaying everyone and everything waiting on the builds.
So, it's ugly, but a practical concession.
I've also answered another similar question which looks at why overloading of f(const T) and f(T) isn't allowed - may be of interest to anyone reading this - Top-level const doesn't influence a function signature
int foo (const int a)
{
a++;
return a;
}
That'll throw an error during compiling.
GNU C and C++ offer the const and pure function attributes.
From the gnu online docs (emphasis mine):
In GNU C and C++, you can use function attributes to specify certain function properties that may help the compiler optimize calls or check code more carefully for correctness. For example, you can use attributes to specify that a function never returns (noreturn), returns a value depending only on the values of its arguments (const), or has printf-style arguments (format).
Where the const attribute seems to be a subset to pure, also taken from the gnu docs:
The const attribute imposes greater restrictions on a function’s
definition than the similar pure attribute. Declaring the same
function with both the const and the pure attribute is diagnosed.
With C++ 11, the constexpr specifier was added.
When applied to functions, is there a difference between the const attribute and the constexpr specifier? Does GCC apply different optimizations?
A similar sounding question is Difference between `constexpr` and `const` . But I think this is not a duplicate. My question is specifically about the function attribute const, which seems to have overlapping functionality with constexpr.
When applied to functions, is there a difference between the const attribute and the constexpr specifier?
There are differences.
Firstly, C does not have constexpr, so you cannot take advantage of it in that language.
Calls to constexpr function can be constant expressions. As such, their result can be used for example as the size of an array. The GNU attributes cannot be used to achieve the same (ignoring GCC VLA language extension).
Constexpr functions are good for taking advantage of pre-calculation at compile time. The GNU attributes are still useful for allowing the compiler take advantage of runtime constness. For example, let's say there is a function that cannot be constexpr - perhaps because it calls a non-constexpr function. But we may know that every call to the function produces same output with no side-effects. Const attribute allows the compiler to not repeat redundant calls.
Another difference is that constexpr functions must be defined inline. Non-constexpr functions don't need to be defined inline.
There is very little overlap between the them. Here is a function that is gnu::const (and therefore gnu::pure) but cannot be constexpr:
[[gnu::const]] int f() {
struct s {
int m;
};
union {
s a;
s b;
};
a.m = 1;
return b.m;
}
You cannot read from the common initial subsequence of a union in a constant expression, so this function never produces a constant expression.
The following is a function that is constexpr but cannot be gnu::pure (and therefore cannot be gnu::const):
constexpr void f(int & x) {
x = 1;
}
This writes to memory, which gnu::pure functions cannot do.
constexpr just means that it can be called at compile time. This has overlap with gnu::const and gnu::pure in that you cannot write to global variables or access volatile memory (all IO operations access volatile memory from C++'s point of view), but they each have other restrictions that make them distinct.
In C++11, constexpr was much more limited. You could not write to any reference parameters, so I think C++11 constexpr was a strict subset of gnu::pure. The following is an example of a C++11 constexpr function that cannot be marked gnu::const, however:
[[gnu::pure]] constexpr int f(int const & x) {
return x;
}
Another difference is that a __attribute__ ((const)) function must return the same output value for the same argument (if I am not wrong; it's the first time I see this attribute ;). That does not need to hold for constexpr functions.
An academic example:
constexpr int rand(int n)
{
std::string_view sv(__TIME__);
return sv.back() % n;
}
std::array<int, rand(10) + 1> a; // exemplary usage
Though rand is constexpr, it may produce different output for the same input argument.
This question is a followup question to C++17: still using enums as constants?.
Legacy constants come in several forms, notably:
#define CONSTANT x
enum { CONSTANT = x };
const /*int/unsigned/whatever*/ CONSTANT = x;
A comment about static constexpr and inline constexpr constants as a replacement got me thinking on the subject of updating our many, many legacy constants (particularly #define constants).
As I understand, an inline constexpr value is basically just substituted in place, like an inlined function (which I've been shown to be wrong about). Conversely, a static constexpr value is stored as part of the binary in a separate area. Assuming I understand correctly, when should one be preferred over the other? My hunch is that, for integral constants, inline constexpr will generally be preferred.
Your go-to for global constants in C++17 should just be:
inline constexpr int CONSTANT = 42;
This gets you a nice, first-class variable that you can use in constant expressions and that won't have ODR-issues. You can take references to it.
Macros bring in the problem of... being macros. Enums are limited to integral types. With constexpr variables, you can have them of any literal type. In C++20, you'll very likely be able to just go wild and write:
inline constexpr std::vector<int> small_primes = {2, 3, 5, 7, 11};
inline constexpr std::string cool_name = "Barry";
It is the only option that allows this.
In C++17, the proper way to replace those old idioms (e.g. #define) in headers in namespace scope is to use constexpr inline variables -- and not static (which is implied: they already have internal linkage).
While typically you won't encounter ODR issues (because integer compile-time constants such as those you describe are rarely ODR-used and there is a provision for their typical usage within inline functions), it is best to mark them as inline now that we have the feature in the language and avoid all problems.
See Should `const` and `constexpr` variables in headers be `inline` to prevent ODR violations? for the technical details about it.
When you attempt to use constexpr with main like this:
constexpr int main()
gcc and clang complain:
error: cannot declare '::main' to be inline
error: 'main' is not allowed to be declared constexpr
Let's see what requirements for constexpr function are:
A constexpr function must satisfy the following requirements:
it must not be virtual
its return type must be LiteralType
each of its parameters must be literal type
What is LiteralType?
A literal type is any of the following
void(since c++14)
scalar type
reference type
an array of literal type
What must the function body include?
null statements
static_assert declarations
typedef declarations and alias declarations that do not define classes or enumerations
using declarations
using directives
exactly one return statement that contains only literal values, constexpr variables and functions.
The following examples:
constexpr int main() { ; }
constexpr int main() { return 42; }
constexpr int main() {
// main defaults to return 0
}
seems to fit all these requirements. Also with that, main is special function that runs at start of program before everything else. You can run constexpr functions from main, and in order for something marked constexpr to be constexpr, it must be run in a constexpr context.
So why is main not allowed to be a constexpr?
No, this is not allowed the draft C++ standard in section 3.6.1 Main function paragraph 3 says:
[...]A program that defines main as deleted or that declares main to be inline, static, or constexpr is ill-formed.[...]
main has to be a run-time function and as Lightness says it makes no sense since you can't optimize main away.
The standard gives the precise signature for main, so the compiler is allowed to reject other signatures. Even more specifically, it prescribes that main cannot be constexpr, static, or some other things.
If you're wondering why, the compiler is allowed to insert code at the beginning of main (to do stuff like initialize global variables, etc.) which could make it non-constexpr (which is why e.g. a program is not allowed to call main explicitly).
It doesn't make any sense to declare main as constexpr for two reasons: 1) It is a run-time function. 2) it may not be called from other functions or recursively.
In my opinion the reason is that it makes no sense to declare main() as a constexpr and the standards committee want the C++ programming language to make sense.
The function main() is a special function that deals with program entry-point initialization - it is not sensible to use it to calculate compile-time values.
Take note of the following C++ code:
#include <iostream>
using std::cout;
int foo (const int);
int main ()
{
cout << foo(3);
}
int foo (int a)
{
a++;
return a;
}
Notice that the prototype of foo() takes a const int and that the definition takes an int. This compile without any errors...
Why are there no compilation errors?
Because it doesn't matter to the caller of the foo function whether foo modifies its copy of the variable or not.
Specifically in the C++03 standard, the following 2 snippets explain exactly why:
C++03 Section: 13.2-1
Two function declarations of the same name refer to the same function if they are in the same scope and
have equivalent parameter declarations (13.1).
C++03 Section: 13.1-3
Parameter declarations that differ only in the presence or absence of const and/or volatile are equivalent. Only the const and volatile type-specifiers at the outermost level of the parameter type specification are ignored in this fashion; const and volatile type-specifiers buried within a parameter type specification are significant and can be used to distinguish overloaded function declarations.
Top-level const (i.e., that applies to the value that's passed, not something to which it points or refers) affects only the implementation, not the interface, of a function. The compiler ignores it from the interface viewpoint (i.e., the calling side) and enforces it only on the implementation (i.e., code in the body of the function).
As others have explained, the Standard says it's ok, and that the compiler can afford to be lenient about enforcing this because it doesn't affect the caller, but nobody's answered why the compiler should choose to be lenient. It's not particularly lenient in general, and a programmer who's just been looking at the interface then dives into the implementation may have it in the back of their mind that a parameter is const when it's not or vice versa - not a good thing.
This leniency allows implementation changes without modifying headers, which using traditional make tools triggers recompilation of client code. This can be a serious issue in enterprise scale development, where a non-substantive change in a low-level header (e.g. logging) can force rebuilding of virtually all objects between it and the applications... wasting thousands of hours of CPU time and delaying everyone and everything waiting on the builds.
So, it's ugly, but a practical concession.
I've also answered another similar question which looks at why overloading of f(const T) and f(T) isn't allowed - may be of interest to anyone reading this - Top-level const doesn't influence a function signature
int foo (const int a)
{
a++;
return a;
}
That'll throw an error during compiling.