After the template function is instantiated your program becomes equivalent to the following:
constexpr
int get_size(int (&)[10])
{
return 10;
}
int main()
{
int xs[10];
constexpr int y = get_size(xs); // HERE.
static_assert(10 == y, "wrong size");
}
Then after function invocation substitution it becomes equivalent to the following:
int main()
{
int xs[10];
constexpr int y = 10; // HERE.
static_assert(10 == y, "wrong size");
}
Function invocation substitution is described under 7.1.5 [dcl.constexpr]/5. Essentially parameters are replaces as if copy-initialized and then subsituted for occurences in the return expression. The return expression then likewise as-if copy-initializes the return value. The resulting expression then becomes the expression that is subsituted for the function call. Only after this is the expression considered if it satisfies the constraints on constant expressions placed by the context. (Note, a quality compiler can of course determine that the constexpr function can never be used successfully after any such operation, and can fail after encounting the function definition, but it doesn't have to)
Also note, just to confuse you this concept is removed in C++14 and replaced with a different concept for how constexpr functions are evaluated. Among other things you will be able to use if statements, for statements and local variables of literal type within constexpr functions.