Should this be a constexpr or not? - c++

Consider this code snippet (godbolt):
#include <cstdio>
#include <string>
#include <string_view>
struct Option
{
std::string_view name;
constexpr Option( std::string_view const n ) noexcept : name{n} {}
};
template< std::size_t N >
class TransformedOption : public Option
{
public:
constexpr TransformedOption( std::string_view const nameStr ) :
Option{ { nameStorage_, N - 1 } }
{
for ( auto i = 0U; i < N; ++i )
{
if ( nameStr[ i ] == '_' ) { nameStorage_[ i ] = '-'; }
else { nameStorage_[ i ] = nameStr[ i ]; }
}
}
private:
char nameStorage_[ N ] = {};
};
template< std::size_t N >
constexpr TransformedOption< N > make( char const (&nameStr)[ N ] ) noexcept
{
return TransformedOption< N >{ nameStr };
}
int main()
{
/*constexpr*/ auto t = make( "abcd_efgh_ijkl_mnop_peqst" );
std::printf( "%s\n", t.name.data() );
return 0;
}
Basically, I want to perform compile-time string transformation by replacing each _ with - and making sure that the final binary contains only the transformed string (not the original).
I've tried Clang 10.0.1, GCC 10.2 and MSVC 19.24 (see above godbolt link). The weird stuff the following:
if constexpr is commented-out in main, then MSVC generates incorrect code (i.e. runtime transformation of string), but both GCC and clang generate correct code (i.e. transformed string constant is embedded into the assembly)
if constexpr is not commented-out in main, then MSVC generates correct code (i.e. transformed string constant is embedded into the assembly), but both GCC and clang fail to compile the code, stating that t is not initialized by constant expression (see godbolt). The weirdest thing is the GCC error message, which outputs the transformed string in its error and states that it's not a constant expression.
Well, which compiler is right, according to the C++ standard? To whom should I report a bug? To GCC and Clang folks or to Microsoft?

The constexpr declaration works in all compilers when t is also declared static.
constexpr static auto t = make( "abcd_efgh_ijkl_mnop_peqst" );
The reason is the string_view. It's a reference type that refers into the object being initialized. So one way or another, you are initializing a contexpr pointer. Now, a constexpr pointer (that is not initialized to a null pointer) may only be initialized with the address of an object with static storage duration.
[expr.const] (emphasis mine)
11 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.
When you declare the object to be of automatic storage duration, the pointer in the string_view is not initialized with the address of a static object. Hence GCC and Clang rightfully complain.
The self reference is what makes this interesting and tricky.

Related

Why does gcc/clang think that a returned constexpr object is not constexpr?

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?
Note:
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.
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_asserts (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.

constexpr begin of a std::array

I am having trouble understanding why both gcc-8.2.0 and clang-7.0.0 reject the following code (live code here):
#include <array>
int main() {
constexpr std::array<int,3> v{1,2,3};
constexpr auto b = v.begin(); // error: not a constexpr
return 0;
}
with the error
error: '(std::array<int, 3>::const_pointer)(& v.std::array<int,3>::_M_elems)'
is not a constant expression (constexpr auto b = v.begin();)
According to en.cppreference.com, the begin() member function is declared constexpr. Is this a compiler bug?
So let's sidestep std::array to make this a bit easier:
template <typename T, size_t N>
struct array {
T elems[N];
constexpr T const* begin() const { return elems; }
};
void foo() {
constexpr array<int,3> v{{1, 2, 3}};
constexpr auto b = v.begin(); // error
}
constexpr array<int, 3> global_v{{1, 2, 3}};
constexpr auto global_b = global_v.begin(); // ok
Why is b an error but global_b is okay? Likewise, why would b become okay if we declared v to be static constexpr? The problem is fundamentally about pointers. In order to have a constant expression that's a pointer, it has to point to one, known, constant thing, always. That doesn't really work for local variables without static storage duration, since they have fundamentally mutable address. But for function-local statics or globals, they do have one constant address, so you can take a constant pointer to them.
In standardese, from [expr.const]/6:
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, [...]
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 function, or a null pointer value, and
[...]
b is none of those things in the second bullet, so this fails. But global_b satisfies the bolded condition - as would b if v were declared static.

`constexpr` variable "used in its own initializer": Clang vs. GCC

This question seems related to an existing one, but I do not understand the "portable workaround" provided in the answer there (involving const auto this_ = this;) and moreover I think the following example is easier to follow.
I am playing with the following snippet of C++17 code (live demo):
#include <iostream>
struct Test {
const char* name_{nullptr};
const Test* src_{nullptr};
constexpr Test(const char* name) noexcept
: name_{name}
{}
constexpr Test(const Test& src) noexcept
: src_{&src}
{
name_ = src_->name_;
src_ = nullptr;
}
};
template<char c>
void print_constexpr_char() {
std::cout << c << std::endl;
}
int main() {
constexpr const char* in = "x";
constexpr auto foo = Test{in};
constexpr auto bar = Test{foo};
std::cout << bar.name_ << std::endl;
print_constexpr_char<bar.name_[0]>();
return 0;
}
Compilation fails with GCC 7.2 while Clang 5.0.0 does not see any problem. The GCC error essentially reads
error: the value of 'bar' is not usable in a constant expression
note: 'bar' used in its own initializer
I am even more confused after realizing that removing the final print_constexpr_char makes the code compile although it still contains the line constexpr auto bar = Test{foo}; which GCC used to complain about ("used in its own initializer").
Which compiler is correct here?
How to understand the GCC note (if not a bug) that "using in its own initializer" is harmful iff the result is subsequently used in a constant expression?
Is there a valid way/workaround to use pointers in a constexpr constructor as an intermediate stage before transforming the object under construction into the final state which can be stored in a constexpr variable?
GCC is correct to reject the code (however the error message could use some work). You cannot use the address of a variable in a constant expression unless that variable has static storage duration.
foo is not static. If you move it outside of main, things will work. Demo
The line marked below is the problem:
constexpr Test(const Test& src) noexcept
: src_{&src} <--- That
Standard reference: (Emphasis mine)
[expr.const]
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:
(5.2) — 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 (8.7), the address of a function, or a null pointer value,

I am confused about a constexpr function?

In C++ Primer, Fifth Edition, §6.5.2:
A constexpr function is defined like any other function but must meet certain restrictions: The return type and the type of each parameter in must be a literal type (§2.4.4, p. 66), and the function body must contain exactly one return statement
but another sentence in this chapter (page 239):
A constexpr function is permitted to return a value that is not a constant
// scale(arg) is a constant expression if arg is a constant expression
constexpr size_t scale(size_t cnt) { return new_sz() * cnt; }
Is it a contradictory summary? I am confused about it.
The return type of scale is literal type?
update:
what's the difference between literal type and constant ?
First of all what I believe the author meant was that a constexpr function does not have to result in a constant expression, which is an expression that can be evaluated at compile time.
A constexpr function will only yield a constant expression if the arguments to the function are also constant expressions and the comment right after says exactly that:
// scale(arg) is a constant expression if arg is a constant expression
and the examples that follow right after also demonstrate this behavior:
int arr[scale(2)]; // ok: scale(2) is a constant expression
int i = 2; // i is not a constant expression
int a2[scale(i)]; // error: scale(i) is not a constant expression
In C++(as opposed to C99) as array size must be a constant expression and so the last case is an error since the argument to scale is not a constant expression.
This is different concept from the return type of the function, which must be a literal type which is any of the following:
void(since c++14) (so that constexpr functions can return void)
scalar type which includes Arithmetic types, enumeration types, pointer types, pointer to member types, std::nullptr_-
t, and cv-qualified versions of these types)
reference type
an array of literal type
class type that has all of the following properties:
has a trivial destructor,
is either
an aggregate type
a type with at least one constexpr (possibly template) constructor that is not a copy or move constructor
all non-static data members and base classes are of non-volatile literal types.
It's not contradictory. As well as mandating that the return type must be of "literal type", the draft standard states that a call to a constexpr function does not have to appear in a constant expression. From the C++11 draft standard:
§7.1.5/7 A call to a constexpr function produces the same result as
a call to a equivalent non-constexpr function in all respects
except that a call to a constexpr function can appear in a constant
expression.
constexpr does nothing but tells the compiler that the value is there in compile time, so you can use it as template argument (for example)
int a1 = 5;
std::array<int, a1> arr1; // error, a is variable
const int a2 = 5;
std::array<int, a2> arr2; // OK
int f1() { return 3; }
std::array<int, f1()> arr3; // error, compiler doesn't know it is const 3
constexpr int f2() { return 3; }
std::array<int, f2()> arr4; // OK
Later you also can:
constexpr int f3() { return f1() + 1; } // error, f1 is not constexpr
constexpr int f4() { return f2() + 1; } // OK
std::array<int, f4()> arr5; // OK
Now about literal types limitation: the function arguments and result types should be literal types (Need clarification on definition of literal type), the very same limitation as template arguments apply (known in compile types).
constexpr std::string f5() { return "hello"; } // error,
// std::string is not literal type
constexpr const std::string& f6() {
static const std::string s = "hello";
return s;
}
template<const std::string& s> SomeClass { ... };
SomeClass<f6()> someObject;

C++ constexpr count constructor calls

I'm trying to static instantiate some objects at compile time. What I need is to set to a member int variable an incremented value. For example, the first object I create will have the 0 value, the second 1, the third 2...
Summarizing I need something like this but it has to work as constexpr.
//header
class MyClass final {
private:
static int IDcount;
public:
const int ID;
constexpr MyClass(args);
//~MyClass();
};
//source
int MyClass::IDcount = 0;
constexpr MyClass::MyClass(args) : ID(MyClass::IDcount++) {
}
Is there a way to achieve this at compile time (without taking the ID as argument of the constructor)
It can't be done the way you've defined it, but there is a non-standard but widely implemented preprocessor trick that could be employed.
#include <iostream>
struct MyClass final {
constexpr MyClass(int myid, const char *myname)
: id(myid), name(myname) {}
int id;
const char *name;
};
constexpr MyClass m[]{
MyClass(__COUNTER__, "Larry"),
MyClass(__COUNTER__, "Moe"),
MyClass(__COUNTER__, "Curly")
};
int main()
{
for (auto const &obj : m)
std::cout << obj.id << '\t' << obj.name << "\n";
}
The __COUNTER__ macro is defined in Microsoft's Visual C++, gcc since version 4.3 and clang.
When run, this program produces:
0 Larry
1 Moe
2 Curly
A constexpr-function or ctor must be a valid core constant expression on at least one path:
7.1.5 The constexpr specifier [dcl.constexpr]
5 For a non-template, non-defaulted constexpr function or a non-template, non-defaulted, non-inheriting constexpr constructor, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression (5.19), the program is ill-formed; no diagnostic required.
Which means it cannot modify a global object on all paths:
5.19 Constant expressions [expr.const]
A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (1.9), would evaluate one of the following expressions:
modification of an object (5.17, 5.2.6, 5.3.2) unless it is applied to a non-volatile lvalue of literal type
that refers to a non-volatile object whose lifetime began within the evaluation of e;