Reputation: 10911
I've tried this with VC++, gcc and clang and I get slightly different results for all of them.
VC++ says it's ok.
if the constexpr
object is not declared static
, then both gcc and clang say something similar:
gcc Demo:
<source>: In function 'int main()':
<source>:141:30: error: 'ArrayStack<int, 2>{int [2]{2, 1}, (((int*)(& y.ArrayStack<int, 2>::m_stack)) + 8)}' is not a constant expression
141 | constexpr auto y { fn2() };
| ^
<source>:142:29: error: non-constant condition for static assertion
142 | static_assert( y.back() == 1, "Should equal 1.");
| ~~~~~~~~~^~~~
<source>:143:44: error: 'ArrayStack<int, 2>{int [2]{2, 1}, (((int*)(& z.ArrayStack<int, 2>::m_stack)) + 8)}' is not a constant expression
143 | constexpr ArrayStack<int, 2> z { fn2() };
| ^
<source>:144:29: error: non-constant condition for static assertion
144 | static_assert( z.back() == 1, "Should equal 1.");
| ~~~~~~~~~^~~~
clang Demo:
<source>:141:20: error: constexpr variable 'y' must be initialized by a constant expression
constexpr auto y { fn2() };
^~~~~~~~~~~
<source>:141:20: note: pointer to subobject of 'y' is not a constant expression
<source>:141:20: note: address of non-static constexpr variable 'y' may differ on each invocation of the enclosing function; add 'static' to give it a constant address
constexpr auto y { fn2() };
^
static
<source>:142:20: error: static assertion expression is not an integral constant expression
static_assert( y.back() == 1, "Should equal 1.");
^~~~~~~~~~~~~
<source>:142:22: note: initializer of 'y' is not a constant expression
static_assert( y.back() == 1, "Should equal 1.");
^
<source>:141:20: note: declared here
constexpr auto y { fn2() };
^
Since clang gave a suggestion of using the static
keyword, I tried that and got these errors:
gcc Demo:
Accepted the assignment of the constexpr
object to a constexpr
variable, BUT didn't allow using a constexpr
member function in a constexpr
context (static_assert
).
<source>:142:29: error: the value of 'y' is not usable in a constant expression
<source>:141:27: note: 'y' used in its own initializer
141 | static constexpr auto y { fn2() };
| ^
<source>:144:29: error: non-constant condition for static assertion
144 | static_assert( z.back() == 1, "Should equal 1.");
| ~~~~~~~~~^~~~
clang Demo:
Accepted both assignment and usage in a constexpr
context.
MSVC++ Demo:
Didn't care if was static or not.
Here is the relevant code (full code can be seen in the Demo links above):
template <typename Type, size_t Size>
class ArrayStack {
public:
using iterator = Type*;
using const_iterator = Type const*;
using value_type = Type;
using pointer = Type*;
using reference = Type&;
using difference_type = std::make_signed_t<size_t>;
using size_type = size_t;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
constexpr ArrayStack()
: m_stack{}
, m_end{ begin() }
{}
// Init only used part of m_stack is only available in C++20
constexpr ArrayStack(ArrayStack const& to_copy)
: m_stack{}
, m_end{ begin() }
{
*this = to_copy;
}
constexpr ArrayStack& operator=(ArrayStack const& rhs) {
// std::copy is constexpr in C++20
auto it{ begin() };
for (auto rhs_it{ rhs.begin() }, rhs_end_it{ rhs.end() };
rhs_it != rhs_end_it;
++it, ++rhs_it)
{
*it = *rhs_it;
}
m_end = begin() + rhs.size();
return *this;
}
//...
constexpr iterator begin() { return m_stack; }
constexpr const_iterator begin() const { return m_stack; }
//...
constexpr iterator end() { return m_end; }
constexpr const_iterator end() const { return m_end; }
//...
constexpr void push_back(value_type const& value) {
if (!full()) {
m_end++[0] = value;
}
else {
throw std::out_of_range("Ran out of stack space");
}
}
//...
constexpr size_t size() const {
return end() - begin();
}
constexpr value_type const& back() const {
if (!empty()) {
return m_end[-1];
}
else {
throw std::out_of_range("Nothing on stack");
}
}
//...
constexpr bool empty() const {
return m_end == begin();
}
constexpr bool full() const {
return end() == m_stack + Size;
}
private:
Type m_stack[Size];
iterator m_end;
};
constexpr bool fn() {
ArrayStack<int, 2> stack;
stack.push_back(2);
stack.push_back(1);
return stack.back() == 1;
}
constexpr auto fn2() {
ArrayStack<int, 2> stack;
stack.push_back(2);
stack.push_back(1);
return stack;
}
int main() {
static_assert( sizeof(int) == 4, "Should be 4 bytes in size");
static_assert( fn(), "Should return true.");
static_assert( fn2().back() == 1, "Should equal 1.");
constexpr auto y { fn2() };
static_assert( y.back() == 1, "Should equal 1.");
constexpr ArrayStack<int, 2> z { fn2() };
static_assert( z.back() == 1, "Should equal 1.");
return 0;
}
This appears to be caused by the pointer in the object. Replacing the pointer with an index to one past the end seems to fix the issue, though the data layout is a bit odd as it seems to have values which shouldn't really contribute to anything as the result should be compile time data (with directives showing) and there is a lot more data than there should be, even at -O3
. Also, the compile time data isn't used, so it should also be just thrown away.
gcc (with directives showing, using -O3
) left is without static
Demo, right is with static
Demo:
...
clang had way too many differences to be able to analyze effectively. They appeared to be byte value changes, label changes and possibly ordering changes.
MSVC++ (show directives doesn't seem to change anything, using -O3
). Left is without static
, right is with static
.
Note that without the directives showing with -O3
, the main()
was the same for gcc and clang (with directives showing and using -O3
) :
main:
xor eax, eax
ret
Question is why is the pointer causing such havoc? Am I correct in assuming that this behaviour is a bug on the gcc/clang's part?
Side question: Why is a static constexpr
variable resulting in different code than a constexpr
variable? As they are both generate compile time constants, they should result in the same code, shouldn't they?
In my index version, there is a small bug where m_iend
isn't assigned to rhs.m_iend
at the end of operator=()
. This doesn't affect the code path depicted, but it is an error if the assignment is done outside of the copy constructor.
Upvotes: 2
Views: 688
Reputation: 29193
MSVC is pretty clearly in error for accepting your code (maybe it's just trying to be "nice"). Clang's error message is to the point: y
would contain a pointer (m_end
) to some part of itself. Pointers are not allowed in the results of constant expressions unless they refer to static
objects. A constexpr
variable must be initialized from a constant expression. So y
needs to be static
to be constexpr
.
[expr.const]
(as of C++20, but much the same in C++14)
A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value satisfies the following constraints:
- if the value is an object of class type, each non-static data member of reference type refers to an entity that is a permitted result of a constant expression,
- if the value is of pointer type, it contains the address of an object with static storage duration, the address past the end of such an object (
[expr.add]
), the address of a non-immediate function, or a null pointer value,- if the value is of pointer-to-member-function type, it does not designate an immediate function, and
- if the value is an object of class or array type, each subobject satisfies these constraints for the value.
An entity is a permitted result of a constant expression if it is an object with static storage duration that either is not a temporary object or is a temporary object whose value satisfies the above constraints, or if it is a non-immediate function.
Clang is also giving the rationale for why C++ is defined this way: the value of a pointer is an address to an object. If said pointer is supposed to always have the same value (address) no matter when you evaluate it (which is what constexpr
means), the object it is referring to must be static
. I'm not sure what you're getting at with the assembly: if y
, z
are static constexpr
then Clang makes main
a no-op. What more could you want?
That GCC chokes on the static_assert
s (but not on the static constexpr auto y{fn2()};
!) does seem to me to be a GCC bug.
Having internal pointers in objects is annoying and error-prone anyway. Use an index.
Upvotes: 1