It is known that std::array::operator[]
since C++14 is constexpr
, see declaration below:
constexpr const_reference operator[]( size_type pos ) const;
However, it is also const
qualified. This causes implications if you want to use the subscript operator of a std::array
in order to assign values to your array at compile time. For example consider the following user literal:
template<typename T, int N>
struct FooLiteral {
std::array<T, N> arr;
constexpr FooLiteral() : arr {} { for(int i(0); i < N; ++i) arr[i] = T{42 + i}; }
};
The above code won't compile if you try to declare a constexpr
variable of type FooLiteral
. This is attributed to the fact that overload resolution rules qualify the non-const qualified, non-constexpr overload of the array's subscript operator as a better match. Thus the compiler complains about calling a non-constexpr
function.
Live Demo
I can't figure out what was the reason for the commitee to declare this overload as const
qualified for C++14, however it seems that the implication is being noticed and there's also a proposal p0107R0 to fix this in the upcomming C++17.
My natural though to overcome this for C++14 was to somehow hack the expression, in order to evoke the correct subscript operator. What I did is the following:
template<typename T, int N>
struct FooLiteral {
std::array<T, N> arr;
constexpr FooLiteral() : arr {} {
for(int i(0); i < N; ++i) {
const_cast<T&>(static_cast<const std::array<T, N>&>(arr)[i]) = T{42 + i};
}
}
};
Live Demo
That is I casted the array to const
reference to evoke the correct subscript operator overload and then I const_cast
the returned object of the overloaded subscript operator to T&
in order remove its const-ness and be able to assign to it.
This works fine, but I know that const_cast
should be used with caution and to be frank I have second thoughts about if this hack can cause undefined behaviour.
Intuitively, I don't think there's a problem, since this const_cast
is taking place at compile time initialization thus, I can't think of an implication that can arise at this state.
But is that so, or am I wrong and this introduce UB to the program?
Can someone justify if this is a UB or not?
As far as I can tell this is not undefined behavior. The proposal that added constexpr to operator[]
happened before the changes that removed the implicit const from constexpr member functions. So it looks like they just added on constexpr without reflecting on the need for keeping const or not.
We can see form an earlier version of Relaxing constraints on constexpr functions that it says the following about mutating literals within a constant expression:
Objects created within a constant expression can be modified within the evalution of that constant expression (including the evaluation of any constexpr function calls it makes), until the evaluation of that constant expression ends, or the lifetime of the object ends, whichever happens sooner. They cannot be modified by later constant expression evaluations. [...]
This approach allows arbitrary variable mutations within an evaluation, while still preserving the essential property that constant expression evaluation is independent of the mutable global state of the program. Thus a constant expression evaluates to the same value no matter when it is evaluated, excepting when the value is unspecified (for instance, floating-point calculations can give different results and, with these changes, differing orders of evaluation can also give different results).
and we can see the earlier proposal I referenced points out the const_cast
hack and it says:
In C++11, constexpr member functions are implicitly const. This creates problems for literal class types which desire to be usable both within constant expressions and outside them:
[...]
Several alternatives have been suggested to resolve this problem:
- Accept the status quo, and require users to work around this minor embarrassment with const_cast.
No UB here, your member arr
is non constant, you can "play" with its const
ness at will (well sort of, you get what I mean)
If your member was a constant expression then you'd have UB, because you have already initialized in the initalizer list and post creation you are not allowed to assume it has mutable value. Do whatever metaprogramming mumbo jumbo you wish inside the initializer list.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With