Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filling a std::array at compile time and possible undefined behaviour with const_cast

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?

Q:

Can someone justify if this is a UB or not?

like image 458
101010 Avatar asked Dec 17 '15 15:12

101010


2 Answers

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.
like image 99
Shafik Yaghmour Avatar answered Sep 28 '22 00:09

Shafik Yaghmour


No UB here, your member arr is non constant, you can "play" with its constness 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.

like image 26
Lorah Attkins Avatar answered Sep 28 '22 01:09

Lorah Attkins