For the following code, why does the first case in main work fine without the redeclaration of Foo::bar, whereas the second case with the function requires it?
struct Foo{
static constexpr int bar = 30;
};
//Declaration of Foo::bar outside of struct
constexpr int Foo::bar;
int returnconstexpr(const int& x) { return x; }
int main()
{
//Ok without declaration outside of struct
std::cout << Foo::bar << std::endl;
//Requires declaration outside of struct
std::cout << returnconstexpr(Foo::bar) << std::endl;
//Here static constexpr works as a definition
static constexpr int x = 2;
std::cout << returnconstexpr(x) << std::endl;
return 0;
}
I am assuming this is because in the first case, the compiler literally just sticks in the value, whereas in the second case the function requires an address which doesn't yet exist without the redeclaration. If that is so, then is the the thing I'm saying is declaration actually a definition? I am confused by this because an initialiser is provided in the class, but it doesn't make it a definition. For example, the third case works just fine.
I am assuming this is because in the first case, the compiler
literally just sticks in the value, whereas in the second case the
function requires an address which doesn't yet exist without the
redeclaration. If that is so, then is the the thing I'm saying is
declaration actually a definition?
You've already answered the question. Static members are defined outside of the class, so what you have is a definition. When you pass that to the function, the address is required and therefore you need to define the static member. While in your first case the compiler simply replaces Foo::bar
with the value.
Now change the function signature to the following:
int returnconstexpr(int x) { return x; }
In the above case you will no longer need the definition.
The rule for this is in 3.2 of the C++ standard:
A variable x whose name appears as a potentially-evaluated expression
ex is odr-used unless x is an object that satisfies the requirements
for appearing in a constant expression (5.19) and ex is an element of
the set of potential results of an expression e, where either the
lvalue-to-rvalue conversion (4.1) is applied to e, or e is a
discarded-value expression (Clause 5).
In the above case, an lvalue-to-rvalue conversion is immediately applied, and therefore it is not odr-used (as the standard says) and a definition is not required. In simple terms, this means it can just use the value and does not need to know the address, however when you use a reference type (const int&), that requires the compiler to know where the object lives in memory.