At the language level, if you pass it by value then, of course, it is passed by value, not by reference. But how the passing is implemented physically under the hood is an implementation detail, which is not that dependent on the argument being const
as it might seem at the first sight.
In many compiler implementations, "large" arguments passed by value (from the language point of view) are actually passed by reference under the hood and then a copy is made by the prologue code of the function itself. The function body uses the copy, which creates the end effect of the parameter being received by value.
In other implementations the argument is literally passed "by value", i.e. the copy is prepared in advance, by the caller.
The advantage of the former approach is that knowing the inner workings of the function, the compiler might decide not to make a copy when it knows that a copy is not needed (e.g. when the function body makes no attempts to modify the parameter value). Note that a smart compiler can perform this analysis and eliminate unnecessary copying regardless of whether the corresponding argument is declared as const
.
This is a matter of optimal code generation, not governed by the language specification. However, such matters might be specified by platform-dependent ABI specs.