Draft N3690 : Why is reference std::array::operator[] not constexpr ?

604 views
Skip to first unread message

thomas....@gmail.com

unread,
Oct 12, 2013, 10:43:09 AM10/12/13
to std-dis...@isocpp.org
Hello,
I'm playing around with the relaxed constraints on constexpr functions with clang + libc++ trunk and noticed something strange with std::array : Because std::array lacks a constexpr "reference operator[]" (23.3.2.1.3), it is impossible to instantiate an std::array in a constepxr function and write in it. So it looks a bit inconsistent because we can instantiate and write in a plain C array or std::tuple, but not std::array :


constexpr int foo()
{
   
int i[] = {1, 2, 3, 4};
   i
[0]= i[1]; // OK
   
return i[0];
}

constexpr int bar()
{
   
auto tup = std::make_tuple(1, 2, 3, 4);
   std
::get<0>(tup) = std::get<1>(tup); // OK because T& std::get<I>(tuple) is constexpr !
   
return std::get<0>(tup);
}

constexpr int baz()
{
   std
::array<int, 4> i = {1, 2, 3, 4};
   i
[0]= i[1]; // error reference operator[] is not constexpr
   
return i[0];
}

int main()
{
 
constexpr int i1 = foo();
 
constexpr int i2 = bar();
 
//constepxr int i3 = baz(); // don't compile
}

I don't understand why reference std::array::operator[] is not constepxr. At first I thought it was to avoid some unsafe usage or constness issue but I can't think of any.
In fact now that we have.relaxed constexpr and that constepxr member function are no longer implicitly const it looks to me that the keyword "constexpr", when placed before a function, is now just some kind of marker for the compiler to check some constraint inside the function (no malloc, no call to non-constepxr function etc.) and to allow this function to be called by others constexpr function.

By the way my original attempt was to try to write a constexpr function that would sort a list of integers at compile time without any template metaprogramming, just classic imperative style. The plan was to take advantage of the fact that std::array<T, N> is a literal type when T is a literal type (3.9.10) so it should be possible not only to use an std::array but also return it from a constexpr function like so :


// don't compile
template <int... i>
std
::array<int, sizeof...(i)> compile_time_sort()
{
   constepxr
int N = sizeof...(i);
   std
::array<int, N> a= {i...};
   
for(int i = 0; i < N ; i++){
     
// plan was to sort the array here, with classic imperative style
     
// but it's impossible because a[i] = something don't compile...
   
}
   
return a;
}

constepxr std
::array<int, 4> sorted_indices = compile_time_sort<4, 6, 1, 9>(); // don't compile

Which doesn't compile because of the lack of constexpr reference operator[].
So instead I ended up doing the sort inside a plain C-array and then copy it into a std::array. It works, but the code is weirder and harder to read :

template<int... x, int... idx>
constexpr auto compile_time_sort_imp(std::integer_sequence<int, x...>,
                                     std
::integer_sequence<int, idx...>)
{
 
constexpr int N = sizeof...(x);
 
int a[] = {x...};
 
for (int i = 0;  i < N - 1;  i++)
 
{
   
// sort the array, with classic imperative style
   
//...

 
}
 
return std::array<int, N>{a[idx]...}; // and copy the result in a std::array
}


template<int... x>
constexpr auto compile_time_sort_()
{
   
auto seq = std::integer_sequence<int, x...>();
   
auto idx = std::make_integer_sequence<int, sizeof...(x)>();

   
return compile_time_sort_imp(seq, idx);
}

constepxr std::array<int, 4> sorted_indices = compile_time_sort<4, 6, 1, 9>(); // OK !

Daniel Krügler

unread,
Oct 12, 2013, 11:05:21 AM10/12/13
to std-dis...@isocpp.org
2013/10/12 <thomas....@gmail.com>:
> Hello,
> I'm playing around with the relaxed constraints on constexpr functions with
> clang + libc++ trunk and noticed something strange with std::array : Because
> std::array lacks a constexpr "reference operator[]" (23.3.2.1.3), it is
> impossible to instantiate an std::array in a constepxr function and write in
> it. So it looks a bit inconsistent because we can instantiate and write in a
> plain C array or std::tuple, but not std::array :

The fact that std::array is applying constexpr to the const overloads
alone is simply a consequence of the fact that the proposing paper was
written at a time, where the rules were different and would not have
allowed that. Fixing the inconsistent difference of constexpr
non-static member functions versus free or static constexpr functions
was very important to get and I think that we really should now
improve the existing standard function specifications.

Unfortunately, during the Chicago meeting it was decided that a
conforming implementation is no longer permitted to add constexpr to
other functions, see

http://cplusplus.github.io/LWG/lwg-defects.html#2013

This means that this makes it now somewhat harder to test it in the
wild (Of-course it will still be possible to define some "extended
mode" where this would be supported).

- Daniel

David Krauss

unread,
Oct 13, 2013, 3:01:05 AM10/13/13
to std-dis...@isocpp.org
On 10/12/13 11:05 PM, Daniel Kr�gler wrote:
> 2013/10/12 <thomas....@gmail.com>:
>> Hello,
>> I'm playing around with the relaxed constraints on constexpr functions with
>> clang + libc++ trunk and noticed something strange with std::array : Because
>> std::array lacks a constexpr "reference operator[]" (23.3.2.1.3), it is
>> impossible to instantiate an std::array in a constepxr function and write in
>> it. So it looks a bit inconsistent because we can instantiate and write in a
>> plain C array or std::tuple, but not std::array :
> The fact that std::array is applying constexpr to the const overloads
> alone is simply a consequence of the fact that the proposing paper was
> written at a time, where the rules were different and would not have
> allowed that. Fixing the inconsistent difference of constexpr
> non-static member functions versus free or static constexpr functions
> was very important to get and I think that we really should now
> improve the existing standard function specifications.
>
> Unfortunately, during the Chicago meeting it was decided that a
> conforming implementation is no longer permitted to add constexpr to
> other functions, see
>
> http://cplusplus.github.io/LWG/lwg-defects.html#2013

There's no rationale mentioned there. I can imagine it's in the interest
of standardization, that implementation divergence on this point defeats
the purpose of having a common standard in the first place. But it would
be good to have some kind of indication besides "it was debated and
opposite decision was made."

The user can't detect whether a function is declared constexpr, only
whether a given function call may appear in a constant expression. Aside
from constexpr functions whose entire definitions are provided, such as
numeric_limits members, there's no guarantee about how any standard
constexpr function must be used to form a constant expression. So this
isn't really standardized yet (or proposed to be) in the first place.

TBH I don't really understand why we need the constexpr qualifier for
functions in the first place. The compiler can never guarantee that any
constexpr qualifier is doing anything useful. It all comes down to use
of the function at the call site. With the newly relaxed rules for C++14
it's particularly ridiculous. See
http://stackoverflow.com/q/14472359/153285 .

The functions being marked as constexpr need to be evaluated on a
case-by-case basis and concrete requirements should be set. Common sense
rules can go into clause 17 [constexpr.functions], but cases such as the
tuple-like get() interface and even forward() and move() need more
elaboration.

Once you've specified in interface documentation that a function can
appear in a constant expression, what does anyone gain from having a
"sometimes this forms a constant expression" annotation in the code?

Gabriel Dos Reis

unread,
Oct 13, 2013, 4:55:34 AM10/13/13
to std-dis...@isocpp.org
David Krauss <pot...@gmail.com> writes:

[...]

| TBH I don't really understand why we need the constexpr qualifier for
| functions in the first place.

I am not sure what you mean by this: (1) that you are unaware of how the
constexpr keyword came to be? or (2) that you know the history but you
just refuse to understand it?


Fast backward to 2003. When the notion of generalized constant
expression function was proposed, there was no keyword; I was confident
that none was needed as long as these functions were required to be
inline. However, various people -- implementers, in particular -- insisted
on a keyword to alert them in advance that they were supposed to handle
invocations of these functions specially, and also because the original
proposal didn't allow local mutation so there was also a concern of
diagnostic quality. There is actually a huge variety in C++
implementation strategies than you would suspect. And at the time, they
were valid points that needed consideration. Further use-cases for the
'constexpr' keyword were discovered. The bottom line was that
the proposal wasn't going anywhere if it didn't have a keyword of a sort.

A decade later, the world looks very different; it is hard to live with
history, but that is the price we pay for progress.

-- Gaby

David Krauss

unread,
Oct 13, 2013, 5:18:00 AM10/13/13
to std-dis...@isocpp.org
On 10/13/13 4:55 PM, Gabriel Dos Reis wrote:
> David Krauss <pot...@gmail.com> writes:
>
> [...]
>
> | TBH I don't really understand why we need the constexpr qualifier for
> | functions in the first place.
>
> I am not sure what you mean by this: (1) that you are unaware of how the
> constexpr keyword came to be? or (2) that you know the history but you
> just refuse to understand it?

I wasn't aware of the history, so thanks!

But why are we stuck with the keyword? For object declarations I think
it makes perfect sense, even if a similar argument could be applied that
the compiler already knows a constant when it sees one. For functions,
though, it deceptively fails to fulfill its apparent (to the uninformed)
guarantee.

Constexpr sort-of documents that a function is supposed to work a
certain way, and in doing so may enable the user to forget the more
precise documentation and manual diagnostic work that he still must do.
See the StackOverflow post. The confusion, and code modifications
required for adoption, run deep enough that a lot of time and effort has
apparently been spent to revise the standard to add the keyword, but not
the underlying guarantees about what usages of the functions qualify.

Making the function qualifier optional would only be a breaking change
for code relying on its absence to prevent constantness. Is that a
deal-breaker?

- D

Gabriel Dos Reis

unread,
Oct 13, 2013, 10:53:46 AM10/13/13
to std-dis...@isocpp.org
David Krauss <pot...@gmail.com> writes:

| On 10/13/13 4:55 PM, Gabriel Dos Reis wrote:
| > David Krauss <pot...@gmail.com> writes:
| >
| > [...]
| >
| > | TBH I don't really understand why we need the constexpr qualifier for
| > | functions in the first place.
| >
| > I am not sure what you mean by this: (1) that you are unaware of how the
| > constexpr keyword came to be? or (2) that you know the history but you
| > just refuse to understand it?
|
| I wasn't aware of the history, so thanks!
|
| But why are we stuck with the keyword?

The Standards usually change only when someone makes a proposal,
champions it, convinces the committee, and it is adopted.

-- Gaby

Richard Smith

unread,
Oct 13, 2013, 3:31:14 PM10/13/13
to std-dis...@isocpp.org
On Sun, Oct 13, 2013 at 2:18 AM, David Krauss <pot...@gmail.com> wrote:
On 10/13/13 4:55 PM, Gabriel Dos Reis wrote:
David Krauss <pot...@gmail.com> writes:

[...]

| TBH I don't really understand why we need the constexpr qualifier for
| functions in the first place.

I am not sure what you mean by this: (1) that you are unaware of how the
constexpr keyword came to be? or (2) that you know the history but you
just refuse to understand it?

I wasn't aware of the history, so thanks!

But why are we stuck with the keyword? For object declarations I think it makes perfect sense, even if a similar argument could be applied that the compiler already knows a constant when it sees one. For functions, though, it deceptively fails to fulfill its apparent (to the uninformed) guarantee.

The constexpr keyword does have utility.

It affects when a function template specialization is instantiated (constexpr function template specializations may need to be instantiated if they're called in unevaluated contexts; the same is not true for non-constexpr functions since a call to one can never be part of a constant expression). If we removed the meaning of the keyword, we'd have to instantiate a bunch more specializations early, just in case the call happens to be a constant expression.

It reduces compilation time, by limiting the set of function calls that implementations are required to try evaluating during translation. (This matters for contexts where implementations are required to try constant expression evaluation, but it's not an error if such evaluation fails -- in particular, the initializers of objects of static storage duration.)

It is also useful as a statement of intent: by marking a function as constexpr, you request that a compiler issues a diagnostic if it can easily see that a call to that function can never appear in a constant expression. There is a limited set of cases in which such a diagnostic is mandatory, and in the other cases it's a quality-of-implementation issue (but in practice compilers do a reasonable job of checking this). This statement of intent is also useful to human readers and maintainers of the code -- when modifying a function marked 'constexpr', you are aware that the function is intended to be used in constant expressions, so you know not to add (for instance) dynamic memory allocation to it, and if you do, the compiler stands a chance of telling you that you broke the users of your library. Obviously this checking is imperfect, because it doesn't provide a guarantee, but it still has some value.

Daryle Walker

unread,
Oct 14, 2013, 3:06:27 AM10/14/13
to std-dis...@isocpp.org, thomas....@gmail.com
Actually, it's worse than you think.  I originally used N3690 as a C++14 reference for my proposal (a future N3794), then I discovered there's a later draft: N3691.  That draft stripped "constexpr" off of all of array's methods, except for size, empty, and max_size.  So my proposal followed the later draft.

I could re-add "constexpr" to various array methods during a revision.  But you have to show that said decorator is the best choice.  Using the "constexpr" decorator forces all implementors to use it for a given method from that point on.  So you have to show that a given method can be implemented as constexpr, that all implementors can do it, and that any alternative would be stupid to do.

Daryle W.

Richard Smith

unread,
Oct 14, 2013, 3:18:18 AM10/14/13
to std-dis...@isocpp.org, thomas....@gmail.com
On Mon, Oct 14, 2013 at 12:06 AM, Daryle Walker <dar...@gmail.com> wrote:
Actually, it's worse than you think.  I originally used N3690 as a C++14 reference for my proposal (a future N3794), then I discovered there's a later draft: N3691.  That draft stripped "constexpr" off of all of array's methods, except for size, empty, and max_size.  So my proposal followed the later draft.

N3691 is supposed to be identical to N3690, other than the cover sheet. If it's not, that's unintended, and you should let the project editor know. (N3690 is the Committee Draft, N3691 is the Working Draft). In this instance, I don't see what you're referring to: in both documents, page 764 contains a constexpr operator[], at(), front() and back().
 
I could re-add "constexpr" to various array methods during a revision.  But you have to show that said decorator is the best choice.  Using the "constexpr" decorator forces all implementors to use it for a given method from that point on.  So you have to show that a given method can be implemented as constexpr, that all implementors can do it, and that any alternative would be stupid to do.

Daryle W.


On Saturday, October 12, 2013 10:43:09 AM UTC-4, thomas....@gmail.com wrote:
Hello,
I'm playing around with the relaxed constraints on constexpr functions with clang + libc++ trunk and noticed something strange with std::array : Because std::array lacks a constexpr "reference operator[]" (23.3.2.1.3), it is impossible to instantiate an std::array in a constepxr function and write in it. So it looks a bit inconsistent because we can instantiate and write in a plain C array or std::tuple, but not std::array :

--
 
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.

Daryle Walker

unread,
Oct 14, 2013, 4:36:19 AM10/14/13
to std-dis...@isocpp.org, thomas....@gmail.com
On Monday, October 14, 2013 3:18:18 AM UTC-4, Richard Smith wrote:
On Mon, Oct 14, 2013 at 12:06 AM, Daryle Walker <dar...@gmail.com> wrote:
Actually, it's worse than you think.  I originally used N3690 as a C++14 reference for my proposal (a future N3794), then I discovered there's a later draft: N3691.  That draft stripped "constexpr" off of all of array's methods, except for size, empty, and max_size.  So my proposal followed the later draft.

N3691 is supposed to be identical to N3690, other than the cover sheet. If it's not, that's unintended, and you should let the project editor know. (N3690 is the Committee Draft, N3691 is the Working Draft). In this instance, I don't see what you're referring to: in both documents, page 764 contains a constexpr operator[], at(), front() and back(). 

[Just checked that the const-mode access methods are marked constexpr in N3691.]

I think N3690 had all methods marked constexpr, even the non-const ones.  (That's newly allowed for C++14.)  Importantly, it had the iterator and data methods constexpr.  (I'm no longer sure since I trashed my copy of N3690 since getting N3691.)

Daryle W.
 

thomas....@gmail.com

unread,
Oct 14, 2013, 5:46:37 AM10/14/13
to std-dis...@isocpp.org
Le samedi 12 octobre 2013 17:05:21 UTC+2, Daniel Krügler a écrit :
The fact that std::array is applying constexpr to the const overloads 
alone is simply a consequence of the fact that the proposing paper was
written at a time, where the rules were different and would not have
allowed that. Fixing the inconsistent difference of constexpr
non-static member functions versus free or static constexpr functions
was very important to get and I think that we really should now
improve the existing standard function specifications.

Thanks Daniel,
I didn't notice that the changes "Relaxing constraints on constexpr functions" were so recent, it makes sense that the adjustment are not yet propagated into the entire library. But l hope that C++14 won't ship with a part of the std library converted to the new meaning of constexpr and the other with constepxr only for const overload. I don't really know about the standard process, can it be considered as some kind of library defect ?

By doing a quick scan of the standard I would say that there is at least two components where marking all member function constexpr should be fairly non-controversial :std::array and std::complex

Both N3690 and N3691 have constexpr applied on all const member of std::array but none on non-const member.

thomas....@gmail.com

unread,
Aug 10, 2014, 10:30:25 AM8/10/14
to std-dis...@isocpp.org, thomas....@gmail.com

Le samedi 12 octobre 2013 16:43:09 UTC+2, thomas....@gmail.com a écrit :
Hello,
I'm playing around with the relaxed constraints on constexpr functions with clang + libc++ trunk and noticed something strange with std::array : Because std::array lacks a constexpr "reference operator[]" (23.3.2.1.3), it is impossible to instantiate an std::array in a constepxr function and write in it. So it looks a bit inconsistent because we can instantiate and write in a plain C array or std::tuple, but not std::array :


constexpr int foo()
{
   
int i[] = {1, 2, 3, 4};
   i
[0]= i[1]; // OK
   
return i[0];
}

constexpr int bar()
{
   
auto tup = std::make_tuple(1, 2, 3, 4);
   std
::get<0>(tup) = std::get<1>(tup); // OK because T& std::get<I>(tuple) is constexpr !
   
return std::get<0>(tup);
}

constexpr int baz()
{
   std
::array<int, 4> i = {1, 2, 3, 4};
   i
[0]= i[1]; // error reference operator[] is not constexpr
   
return i[0];
}

int main()
{
 
constexpr int i1 = foo();
 
constexpr int i2 = bar();
 
//constepxr int i3 = baz(); // don't compile
}


I'm a bit concerned, relaxed constrexpr was voted into the core language more than one year ago, but library like std::array are not updated yet.

I just want to say again that with the current draft we can use and modify plain C-array in constexpr function but not std::array, it should be a defect, no ?
Reply all
Reply to author
Forward
0 new messages