The following code compiles in clang 7+, but not in 5 & 6 (with c++17 and c++14).
The problem for clang 5 and 6 seems to be that the implicit copy ctor reads from the mutable member x.
Can anybody tell me, if the whole construction is standard-compliant (c++17), or if the program is ill-formed? Or was there a change in the standard regarding the implicit copy-ctor which might not be implemented in the earlier clang versions?
struct Foo {
int a;
mutable int x{};
constexpr Foo() : a(0) {}
//constexpr Foo(const Foo& other) : a(other.a) {} // <- with this line it works on Clang 5 & 6, too
};
struct FooFactory {
static constexpr auto create() {
auto f = Foo{};
return f;
}
};
int main() {
constexpr Foo f = FooFactory::create();
++f.x;
}
Live code here.
This is a well-formed C++17 program. A constexpr function can of course read (and write) non-constant variables:
constexpr int f(int i) {
int j=i;
++j;
return i+j; // neither is a constant expression
}
The rule is that anything examined in a constant expression must either be a constant or have its lifetime begun during the expression’s evaluation. In your case, the lifetime of create’s f.x plainly begins within the evaluation of the constant expression that is the initialization of main’s f. It is true, however, that no Foo object can be copied by a constant expression that does not also create that object, whether or not it is constexpr.
The only other candidate problem is if the copy constructor were not constexpr, but those requirements are very weak. The only relevant ones are that every (non-variant) member be initialized, which is certainly satisfied, and that it be usable in at least one constant expression, which has been demonstrated.
The code is ill-formed. For the reference, here is simplified code, which fails on all compilers:
struct Foo {
int a;
mutable int x{};
constexpr Foo() : a(0) {}
};
int main() {
constexpr Foo f;
constexpr Foo f1 = f;
}
As per [expr.const] 7.7,
A variable is usable in constant expressions after its initializing
declaration is encountered if
it is a constexpr variable,
or ... (3.5) a non-mutable
subobject or reference member of any of the above.
This disqualifies default copy constructors.
Related
I'm running into an error when trying to access a constexpr member variable of a derived class through a base class reference via CRTP;
template <typename Der>
struct Base
{
constexpr std::size_t getsize()
{
constexpr const auto &b = static_cast<Der*>(this)->arr;
return b.size();
//return static_cast<Der*>(this)->arr.size(); // this works
}
};
struct Derived : Base<Derived>
{
static constexpr std::array<int, 10> arr = {};
};
int main(){
Derived d;
return d.getsize();
}
Error:
<source>:11:31: error: constexpr variable 'b' must be initialized by a constant expression
constexpr const auto &b = static_cast<Der*>(this)->arr;
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:24:14: note: in instantiation of member function 'Base<Derived>::getsize' requested here
return d.getsize();
^
<source>:11:53: note: use of 'this' pointer is only allowed within the evaluation of a call to a 'constexpr' member function
constexpr const auto &b = static_cast<Der*>(this)->arr;
^
1 error generated.
Compiler returned: 1
Update
It turns out that removing the constexpr in the reference works. I'd like to understand why this works ?
auto &b = static_cast<Der*>(this)->arr;
return b.size();
Informally, you should imagine there being a rule that a constexpr variable defined inside a function is required to be initialized by an expression that is always a constant expression regardless of the circumstances under which the function is called. In particular, you can always do something like this:
Base<Derived> b;
b.getSize();
in which case static_cast<Der*>(this)->arr is not a constant expression, since it is in fact UB. Because of the possibility of things like this, your function can't compile at all even though you may never call it in this manner anyway.
The actual rule you are violating is [expr.const]/5.1. A constant expression E may not evaluate this, unless it's by (directly or indirectly) calling some member function inside of which the evaluation of this occurs. In particular, this means that if we have some code like this:
// namespace scope
struct S {
constexpr const S* get_self() {
const S* result = this;
return result;
}
};
constexpr S s;
constexpr const S* sp = s.get_self();
Here, s.get_self() is a constant expression, because the access to this only occurs inside the get_self() function, which is part of the evaluation of s.get_self(). But we cannot make result constexpr, because if it were so, we no longer get to "count" the enclosing function; the initialization step would have to qualify in and of itself as a constant expression, which it is not, since the access to this is now "bare".
For your code this implies that getsize() actually may return a constant expression (for those calls that do not trigger UB as described above) but b cannot be made constexpr.
I have this code:
#include <string>
class A {
public:
// A(A const &) = delete; // Code fails if this is uncommented.
explicit A(int);
explicit A(::std::string const &);
private:
::std::string myname_;
int foo_;
};
static constexpr bool which = false;
A test(::std::string const &s, int a)
{
if constexpr (which) {
A x{a};
return x;
} else {
A y{s};
return y;
}
}
This code fails if A has a deleted copy constructor. But, given the rules about return type for functions with if constexpr in them, it seems like the compiler should be applying RVO here.
Is there a reason why it isn't, other than it being an overlooked case in the language specification?
This has nothing to do with if constexpr
Simply this code is not allowed to compile:
class A {
public:
A(A const &) = delete;
explicit A(int);
};
A test(int a)
{
A x{a};
return x; // <-- error call to a deleted constructor `A(A const &) = delete;`
}
The changes in C++17 you are thinking of have to do with temporary materialization and don't apply to NRVO because x is not a prvalue.
For instance this code was illegal pre C++17 and now it is allowed:
A test(int a)
{
return A{a}; // legal since C++17
}
This is NRVO, which is non-mandatory copy elision:
(emphasis mine)
In a return statement, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a
function parameter or a catch clause parameter, and which is of the
same class type (ignoring cv-qualification) as the function return
type. This variant of copy elision is known as NRVO, "named return
value optimization".
This is an optimization: even when it takes place and the copy/move
(since C++11) constructor is not called, it still must be present and
accessible (as if no optimization happened at all), otherwise the
program is ill-formed:
BTW: Note that in your code, both the if part and else part of the constexpr if statement will be checked.
Outside a template, a discarded statement is fully checked. if
constexpr is not a substitute for the #if preprocessing directive:
void f() {
if constexpr(false) {
int i = 0;
int *p = i; // Error even though in discarded statement
}
}
Does it ever make sense to have a constexpr move constructor?
For example, consider the following:
#include <array>
class C
{
public:
constexpr C(std::array<int, 3> ar) : m_ar{ar} {}
constexpr C(C&& other) : m_ar{std::move(other.m_ar)} { }
private:
std::array<int, 3> m_ar;
};
int main()
{
constexpr C c1 {{{1, 2, 3}}};
constexpr C c2{std::move(c1)};
return 0;
}
This doesn't compile, since despite calling std::move on c1, the compiler deduces it needs to use the (implicitly deleted) copy constructor, not the move constructor. I'm not sure why.
But if I remove the constexpr from c1, then it becomes unusable by the constexpr move constructor.
Is there any way to get this to work? Or is this a bad example for a constexpr move constructor, but there are good examples? Or, is it just always wrong to have a constexpr move constructor?
In principle, a move constructor can be used with a non-const object whose lifetime started during the evaluation of the constant expression:
// C++14
constexpr int f() {
C a(/* ... */);
C b = std::move(a);
return 0;
}
constexpr int i = f();
Similar things can be done in C++11, e.g.
constexpr C foo(C&& c) {
return std::move(c);
}
constexpr int f() {
return foo(C()), 0;
}
That said, since everything used in a constant expression is required to be trivially destructible, the usefulness of a move constructor is rather limited.
This doesn't compile, since despite calling std::move on c1, the compiler deduces it needs to use the (implicitly deleted) copy constructor
c1 is of type C const. When you move() it, that's really a cast to rvalue reference, so you get a C const&&. Note that it's still const. When we perform overload resolution, there are three constructors:
C(std::array<int, 3> ); // not viable
C(C&& ); // not viable
C(C const& ) = delete; // viable!
C const&& can't bind to C&& for the same reason that C const& can't bind to C&. We're left with just the copy constructor, which is implicitly deleted.
Having a constexpr move constructor could make sense - but the object you're "moving" from can't really be moved from, because it's presumably const. You could add a const move constructor:
constexpr C(C const&& other) : m_ar(other.m_ar) { }
This is a glorified copy constructor, but it allows the syntax you want. May as well just allow copying.
I have the following piece of code, that behaves as expected on gcc and clang. However, MSVC gives me an unexpected result.
Lets first look at the problematic code.
#include <iostream>
// -----------------------------------------------
class Test // Dummy for MCVE
{
public:
Test();
void Print();
private:
int arr[5];
};
Test tst;
// -----------------------------------------------
template<typename T>
struct range // some stuff not needed by example removed
{
constexpr range(T n) : b(0), e(n) {}
constexpr range(T b, T e) : b(b), e(e) {}
struct iterator
{
T operator*() { return i; }
iterator& operator++() { ++i; return *this; }
bool operator!=(iterator other) { return i != other.i ; }
T i;
};
iterator begin() const { return{ b }; }
iterator end() const { return{ e }; }
private:
T b,e;
};
constexpr range<int> coord(5);
// -----------------------------------------------
Test::Test()
{
for(auto i : coord)
arr[i]=i;
}
void Test::Print()
{
for(auto i : coord)
std::cout << arr[i] << std::endl;
}
// -----------------------------------------------
int main()
{
tst.Print();
}
Now, on both clang and gcc this prints '0 1 2 3 4'
However, on MSVC this prints '0 0 0 0 0'
The reason being that when the constructor on the global variable tst
runs, 'coord' have yet not been initialized (to 0,5), but it is also not random, but rather (0,0).
To me it would make sense that constexpr initialization happens before regular initialization. Is MSVC conformant in this behaviour?
I should perhaps note that I'm using MSVC version 14.0.22823.1, and
that the expected result can be obtained by changing the order of
the declarations
For static storage duration objects initialization must happen in this order:
Zero-initialization.
Constant initialization (i.e. constexpr).
Dynamic initialization.
Relevant standardeese,
C++14 §3.6.2/2:
” Variables with static storage duration (3.7.1) or thread storage duration (3.7.2) shall be zero-initialized (8.5)
before any other initialization takes place. […] Constant initialization is performed: […] if an object with static or thread storage duration is initialized by a constructor call, and if the
initialization full-expression is a constant initializer for the object; […] Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place.
The same paragraph defines (breaking the flow of text, so I removed it above)
” A constant initializer for an object o is an expression that is a constant expression, except that it may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types.
In the reported example, which I've verified with Visual C++ 2015, the dynamic initialization of the static storage duration object tst takes place before the constant initialization of the static storage duration object coord, and that's a compiler bug.
In C++11, are the aggregates allowed to be copied with curly-braces syntax? I have the following code:
struct s
{
int x;
};
template<class T>
struct holder
{
template<class A>
holder(A&& x) : t{x} {}
T t;
};
Each one of the statements below works.
auto s1 = s{1};
auto s2(s1);
auto s3{s1}; ///NOTE : this works!
However, the second statement below raises the error cannot convert 's' to 'int' in initialization.
holder<s> h{5};
holder<s> h1{s{5}};
I am using gcc 4.8.2. Why do I get this error?
In C++11, when a type T is an aggregate type, initialisation using { ... } performs aggregate initialisation. Aggregate initialisation always initialises the members of T, not T itself.
Although this is exactly what the standard requires, this is unwanted, which is why in a future standard, the rule will likely be changed to make a special exception for initialisation from the same type. This is core language issue 1467.
Until that time, unfortunately, the error you are getting is entirely correct, and you will have to work around it.
First of all this code
struct s
{
int x;
};
template<class T>
struct holder
{
template<class A>
holder(A&& x) : t{x} {}
T t;
};
int main()
{
holder<s> h{5};
return 0;
}
is compiled successfuly.
The invalid statement is
holder<s> h1{s{5}};
The problem is that in fact you are trying to execute
s s1 = { s{5} };
However the compiler is unable to convert an object of type s to an object of type int (that to initialize s1.x) when it tries to make assignment
s.x = s{5};