constexpr constructor which may never be constexpr instantiated - c++

I have come across a case of constexpr usage where I don't understand why the code compiles. Here is a minimal and reproducible example (Godbolt):
struct A
{
A(int a_)
:m_a{a_}
{
}
int m_a;
};
// Compilation fails with this structure:
struct B
{
constexpr B(A a_)
:m_a{a_}
{
}
A m_a;
};
// Compilation OK here, even if A is not constexpr.
struct BOK
{
constexpr BOK(const A& a_)
:m_a{a_}
{
}
A m_a;
};
int main()
{
// constexpr A a{2}; // Fails. OK, A's constructor is not constexpr
// constexpr B b{A(2)}; // Fails. OK, A's constructor is not constexpr
// constexpr BOK bok{A(2)}; // Fails. OK, A's constructor is not constexpr
const BOK bok{A(2)};
return 0;
}
I understand that B does not compile and the error is clear:
error: invalid type for parameter 1 of 'constexpr' function 'constexpr B::B(A)'
What I don't understand if why BOK does compile (especially since B doesn't). It seems the const& somehow makes it OK for the compiler. As I am showing in the main function, trying to instantiate a constexpr version of BOK fails to compile with error:
error: the type 'const BOK' of 'constexpr' variable 'bok' is not literal
as expected, but the struct's definition seems to be fine. It is weird because is seems the compiler thinks there might be a possibility for this to be constexpr... Is there? Why does this work?
I have tried this with both g++ and clang with the -std=c++20 switch.

If you look solely the declaration of B's constructor:
constexpr B::B(A a_);
The compiler knows that this can not possibly be constexpr, since it would have to copy the non-literal type A.
If you look at BOK's constructor:
constexpr BOK::BOK(const A& a_);
This could be called in a constant expression (Like extern A value; BOK{value}), if you don't look at the actual definition.
Until C++23, it is "ill-formed, no diagnostic required" if there is no way to call a constexpr function or constructor in a constant expression. constexpr B::B(A a_) can be ruled out fast, and that's all that GCC is willing to check (it's a trade off for slower compile times vs more correctness). Your BOK struct's constructor is also ill-formed, but the compiler just isn't warning you.
As a side note, you can make A a literal type with any constexpr constructor:
#include <bit>
struct A
{
A(int a_)
:m_a{a_}
{
}
int m_a;
constexpr A() = delete; // Now a literal type
};
struct B
{
constexpr B(A a_)
:m_a{a_}
{
}
A m_a;
};
struct BOK
{
constexpr BOK(const A& a_)
:m_a{a_}
{
}
A m_a;
};
constexpr A a{std::bit_cast<A>(2)};
constexpr B b{std::bit_cast<A>(2)};
constexpr BOK bok{std::bit_cast<A>(2)};

Related

C++ why can't classes with const reference member variables be created using constexpr?

In the following code, I try to store a const reference to another class:
struct A {
};
struct B {
constexpr B(A const & _a) : a(_a) {}
A const & a;
};
int main() {
constexpr A s1;
constexpr B s2{s1};
}
however, the compiler (gcc 11.1) complains with:
cctest.cpp: In function ‘int main()’:
cctest.cpp:12:22: error: ‘B{s1}’ is not a constant expression
12 | constexpr B s2{s1};
|
and I can't work out why s1 is not considered a constant expression. s1 itself is a constexpr in the code. I know this probably has something to do with lifetimes of the references, but I can't work out the logic. In the code that this example came from, I don't want to store a copy of A, I really do just want a reference or (smart) pointer. So:
Why is s1 not a constant expression?
What is the best practice way of handling this?
Many thanks!
Clang 12.0.0+ gives a descriptive note about the issue:
note: address of non-static constexpr variable 's1' may differ on each invocation of the enclosing function; add 'static' to give it a constant address
So you need to add a static here:
struct A {
};
struct B {
constexpr B(A const & _a) : a(_a) {}
A const & a;
};
int main() {
constexpr static A s1;
constexpr B s2{s1};
}

clash of deleted template constructor and user defined conversion operator

I came around a strange error with MSVC compiler.
Let's say that I have class Foo, that must not be constructed from anything but uint32_t (copy, move and default = ok).
I also have a class Bar, that defines a Foo conversion operator.
#include <cstdint>
struct Foo
{
Foo() = default;
constexpr Foo(const Foo&) = default;
constexpr Foo(Foo&&) = default;
// may be constructed only from uint32
constexpr Foo(uint32_t word)
{}
// This expression is required to
// prevent construction from types that are
// not uint32_t or copy or move
// See 9.5.3 Deleted definitions for an example of
// this specific use case.
template <typename T>
constexpr Foo(T) = delete;
};
struct Bar
{
constexpr operator Foo () const { return uint32_t(); }
};
int main()
{
constexpr Foo foo = Bar();
return 0;
}
This compiles fine with MinGW, but MSVC gives an error:
main.cpp(27): error C2280: 'Foo::Foo<Bar>(T)': attempting to reference a deleted function`
Is this behavior defined by the standard? Or is it a MSVC bug? Or maybe MinGW does something wrong?
The error can be eradicated by declaring the delete'd template constructor as explicit.

constexpr and mutable member and implicit copy-ctor

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.

Clang vs. GCC when static_cast'ing to a move-only type

Consider the following simple move-only class:
struct bar {
constexpr bar() = default;
bar(bar const&) = delete;
bar(bar&&) = default;
bar& operator=(bar const&) = delete;
bar& operator=(bar&&) = default;
};
Now, let's create a wrapper:
template <class T>
struct box {
constexpr box(T&& x)
: _payload{std::move(x)}
{}
constexpr explicit operator T() &&
{
return std::move(_payload);
}
private:
T _payload;
};
And a test:
int main()
{
auto x = box<bar>{bar{}};
auto y = static_cast<bar&&>(std::move(x));
}
Clang-6.0 is happy with this code if compiled with -std=c++14 or above. GCC-8.1 however produces the following error:
<source>: In function 'int main()':
<source>:29:45: error: invalid static_cast from type 'std::remove_reference<box<bar>&>::type' {aka 'box<bar>'} to type 'bar&&'
auto y = static_cast<bar&&>(std::move(x));
Here's a link to compiler explorer to try it out.
So my question is, who's right and who's wrong?
Simplified example:
struct bar
{
bar() = default;
bar(bar const&) = delete;
bar(bar&&) = default;
};
struct box
{
explicit operator bar() && { return bar{}; }
};
int main() { static_cast<bar&&>(box{}); }
live on godbolt.org
First of all, let's see what it means for a conversion operator to be explicit:
A conversion function may be explicit, in which case it is only considered as a user-defined conversion for direct-initialization. Otherwise, user-defined conversions are not restricted to use in assignments and initializations.
Is static_cast considered direct-initialization?
The initialization that occurs in the forms
T x(a);
T x{a};
as well as in new expressions, static_­cast expressions, functional notation type conversions, mem-initializers, and the braced-init-list form of a condition is called direct-initialization.
Since static_cast is direct-initalization, it doesn't matter whether the conversion operator is marked explicit or not. However, removing explicit makes the code compile on both g++ and clang++: live example on godbolt.org.
This makes me believe that it's a g++ bug, as explicit makes a difference here... when it shouldn't.

c++ explicit constructor called in implicit situation

I compiled the code below using g++ 6.3.0, with -std=c++14 option.
#include <utility>
#include <iostream>
struct A{
int x;
A(const A&)=default;
A(int x):x(x){}
};
struct B{
A a;
template<class... Args>
B(Args&&... args):a(std::forward<Args>(args)...){
std::cout<<"1!"<<std::endl;
}
explicit B(const A& a):a(a){std::cout<<"2!"<<std::endl;}
};
struct C:B{
using B::B;
};
int main(){
A a{2};
const A& aref=a;
C c=aref; //Implicit conversion
}
I expected it to output "1!" since the conversion is implicit, but it output "2!". If I comment out the template constructor, it will not compile. Is this the correct behavior, or is this some kind of g++ bug?
Yes, your program shall print 1!.
A simplified version of the program showing compiler divergence is
struct B {
int b;
constexpr B(auto) { b = 1; }
constexpr explicit B(int) { b = 2; }
};
struct C : B {
using B::B;
};
constexpr B b = 0;
static_assert( b.b == 1 ); //ok everywhere
constexpr C c = 0;
static_assert( c.b == 1 ); //ok in Clang only
Demo: https://gcc.godbolt.org/z/hva4f5qs5
As quoted in related GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85251 : [namespace.udecl] p13 does specify what should happen here:
Constructors that are named by a using-declaration are treated as though they
were constructors of the derived class when looking up the constructors of the
derived class ([class.qual]) or forming a set of overload candidates
([over.match.ctor], [over.match.copy], [over.match.list]).
So explicit constructor from B shall remain explicit in C after using B::B;, and only Clang correctly process the above program at this moment.
And even easier way to see that other compilers are wrong, is to remove B(auto) constructor:
struct B {
int b;
constexpr explicit B(int) { b = 2; }
};
struct C : B {
using B::B;
};
constexpr C c = 0; // error everywhere
Now all compilers correctly reject the code (demo: https://gcc.godbolt.org/z/P3zqxMvvn) even though they did not call the removed constructor in the previous example.