-
Notifications
You must be signed in to change notification settings - Fork 11.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[regression] std::array<int, 0>::begin() should be constexpr but isn't #39471
Comments
assigned to @ldionne |
The only way I think this could be feasible implemented is by changing the iterator types of array<T, 0>, because we can't feasibly produce a non-null pointer to T without having actually constructed a T in a constant expression. Changing the iterator types is an ABI break, and it will break plenty of existing code that depends on the iterator type being a pointer. I'll add a comment to LWG 2157 that we should consider removing constexpr on a lot of array<T, 0>. |
Interesting. Is it crazy to suggest returning nullptr? A la Eric Niebler in the "Pythagorian Triples, Revisited" example of his recent blog post http://ericniebler.com/2018/12/05/standard-ranges/ ? |
If LWG 2157 goes through as is, then it will forbid returning nullptr (https://cplusplus.github.io/LWG/issue2157) It states:
|
Thanks for this. Again, interesting. In the C++17 draft (n4659.pdf), "26.3.7.8 Zero sized arrays [array.zero]" point 2 says "In the case that N == 0, begin() == end() == unique value. The return value of data() is unspecified.". Does the "unique value" in that text mean that nullptr is already an invalid approach in C++17? Would you hope/expect that your proposed change (that would make If not, what's the best way to make libc++ support C++17's constexpr requirements? Or does it have to choose between violating that or something else? It looks like libstdc++ doesn't specialise for zero-size - is that violating some other part of the C++17 standard? It seems that it'd be possible to keep the constexpr in Thanks very much. |
I think all your analysis is correct. It is currently not allowed to return nullptr, and if we had reinterpret cast in constexpr then it would be trivial to implement (I think it may be coming down the pipeline?) |
Hi, I was able to fix it locally: change std::array<T,0>::data():
Add constexpr to begin()/end() and other functions, I tried the change with clang/gcc/msvc/icc. Correct me if I'm wrong about it. |
Hi Hana, That should be equivalent to reinterpret_cast, and Clang and GCC should diagnose it as an invalid constant expression. For example: https://godbolt.org/z/7RDPHv I'm not sure how you got your result. |
Hmm. Further investigation suggests GCC tolerates this in constant expressions, Clang does not. I'm pretty sure Clang is correct here, at least W.R.T. C++17. |
Here's a better reproducer: https://godbolt.org/z/Xg5aSM |
We might be able to use bitcast to fix this. |
+Richard Smith Do you have suggestions for generating a singular iterator/pointer value in constexpr? |
Here's the related GCC bug about accepting casts from void* in constant expressions: gcc.gnu.org/#65799 |
You should be able to specialize array<T, 0> to use a union containing a T (which is never the active union member, but can have its address taken), in the case where T has a trivial destructor: template requires __is_trivially_destructible(T)
} u; constexpr array() : u() {} This'll result in array<T, 0> having the same size and alignment as T, which appears to match the current libc++ implementation. (This is theoretically inefficient; sizeof(array<T, 0>) need be only alignof(T), but it happens to be convenient for the purposes of this bug!) Presumably only handling the case where T has a trivial destructor is sufficient -- at least prior to C++20 -- because it's a prerequisite for T being a literal type, which is (hopefully!) a prerequisite for array<T, N>::begin() being usable in constant expressions. To support C++20 and beyond, you'll also need to use a union for the remaining cases (but one with a constexpr, non-defaulted destructor). |
|
Any idea why we say that? It appears to have no observable effect, other than to change the value produced by std::is_aggregate (which as I said at the time, we should never have added to the language...) =/ As a terrible hack, we could specialize is_aggregate<array<T, 0>>, but I'd prefer that we throw this back to LWG and ask them to give us implementable rules. :) |
I think making |
I don't think there's any way to make array<T, 0>{} valid but array<T, 0>{{}} ill-formed if you do that. But that's probably acceptable. |
This is not great, but it actually might work: https://gist.github.com/zoecarver/cdc4d24b7adac8c0f36edee156cc2f19 It allows for it to be an aggregate type where data is a constexpr member while erroring for array<T, 0>{{}}. |
Interesting idea. It requires C++14, though (non-static data member initializers make the struct a non-aggregate in C++11); when was 'constexpr' added to these member functions? |
C++17, so I think we are good :) |
*** Bug llvm/llvm-bugzilla-archive#43890 has been marked as a duplicate of this bug. *** |
Another attempt to fix this: https://reviews.llvm.org/D80452 It returns nullptr from an empty array's begin() and end() -- read the review for a justification of why I think it's a valid pragmatic approach given the current situation. |
This should be fixed by:
|
mentioned in issue llvm/llvm-bugzilla-archive#43890 |
Extended Description
Compiling the following with
-fsyntax-only -std=c++17 -stdlib=libc++
:I get:
It looks like the zero-size specialisation of
::std::array
has some constexprs missing. From what I can see in "26.3.7.1 Class template array overview [array.overview]" and "26.3.7.8 Zero sized arrays [array.zero]" of the C++17 draft, a zero-sized array should have constexprbegin()
(etc).This appears to be a regression: on Godbolt, I'm seeing this fail to compile in this way under 7.0 and trunk, but compile cleanly under 6.0.
Thanks very much for all work on libc++.
The text was updated successfully, but these errors were encountered: