constrexpr constructor inherited from shared_ptr - c++

I want to implement my own pointer (with few helper methods) extended from shared_ptr.
class Event;
class EventPtr : public std::shared_ptr<Event> {
public:
constexpr EventPtr()
: std::shared_ptr<Event>() {
}
constexpr EventPtr(std::nullptr_t p)
: std::shared_ptr<Event>(p) {
}
explicit EventPtr(Event* ptr)
: std::shared_ptr<Event>(ptr) {
}
};
The problem is that compiler gives me the following error for both constexpr constructors:
constexpr constructor never produces constant expression
Tell me please how to fix it.

The rules on constexpr constructors changed between C++11 and C++14; see DR1911 constexpr constructor with non-literal base class and this bug.
The fix is to compile in C++14 mode (-std=c++14).
Language in C++11 [dcl.constexpr]:
For a constexpr function, if no function argument values exist such that the function invocation substitution would produce a constant expression (5.19), the program is ill-formed; no diagnostic required. For a constexpr constructor, if no argument values exist such that after function invocation substitution, every
constructor call and full-expression in the mem-initializers would be a constant expression (including conversions), the program is ill-formed; no diagnostic required.
Under C++11, shared_ptr can have constexpr constructors but any class type inheriting from shared_ptr or with a shared_ptr member cannot, because shared_ptr is not a literal type (it has a destructor) and so cannot appear in a constant expression. For C++14 this was simplified to:
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.
Unfortunately this makes all constexpr constructors of non-literal types undefined behavior; DR1911 fixed this by adding a subclause (bolded below):
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.20), or, for a constructor, a constant initializer for some object (3.6.2), the program is ill-formed; no diagnostic required.
struct X { ~X() {} constexpr X() {} }; // OK in C++11, UB in C++14, OK since DR1911
struct Y : X { constexpr Y() : X() {} }; // UB in C++11, UB in C++14, OK since DR1911

Related

Can an object of class type T be constant initialized when T has a non-trivial destructor?

Let's look at this sample of code:
class D
{
public:
constexpr D(int val) : i(val) { };
~D() { };
private:
int i;
};
D d(3);
According to the documentation, D should be constant initialized:
Only the following variables are constant initialized: [...]
2. Static or thread-local object of class type that is initialized by a
constructor call, if the constructor is constexpr and all constructor
arguments (including implicit conversions) are constant expressions,
and if the initializers in the constructor's initializer list and the
brace-or-equal initializers of the class members only contain constant
expressions.
Indeed, d is initialized by constructor call, the constructor of D is constexpr and my argument (3) is a constant expression.
However, to specify to the compiler the value of a variable can be evaluated at compile time, it is possible to use constexpr specifier. But, in this case, it won't compile because D is not a LiteralType because it define a non-trivial constructor.
So, in my snippet, is d really constant initialized? If so, why can't I use constexpr specifier?
So, in my snippet, is d really constant initialized? If so, why can't I use constexpr specifier?
Yes, it will be constant initialized. As you've quoted, constant initialization doesn't need the type to be a LiteralType. But constexpr does need it. Your type is not a LiteralType, so it cannot be a constexpr. But the type and constructor call fulfills the requirements of being constant initialization.
Btw., C++20 will have constinit. With this, you can make sure that a variable gets static initialized (which means constant initialization in your case).
You can check out constinit for your example on godbolt, as a further evidence that it compiles successfully, and you can see that the object is initialized at compile-time (not a requirement by the standard, but GCC does it).

Criteria for a variable to be usable in a constant expression

This code compiles just fine on all big 4 compilers, even on -pedantic
struct S
{
constexpr S(int a) {}
};
constexpr int f(S a)
{
return 1;
}
int main()
{
int a = 0;
S s(a);
constexpr int b = f(s);
}
However, this shouldn't be so according to the standard... right? Firstly, s wouldn't be usable in constant expressions [expr.const]/3, because it fails to meet the criteria of being either constexpr, or, const and of enum or integral type.
Secondly, it is not constant-initialized [expr.const]/2 because the full expression of the initialization would not be a constant expression [expr.const]/10 due to a lvalue-to-rvalue conversion being performed on a variable (a) that is not usable in constant expressions when initializing the parameter of the constructor.
Are all these compilers just eliding the initialization of the parameter of the constructor because it has no side-effects, and is it standard conforming (I'm 99% sure it isn't, as the only way for it to so would be to make s constexpr, and to pass it either a const int or a int that is declared constexpr)?
I believe the magician's trick here is the copy c'tor of S. You omitted it, so a defaulted one is generated for you here. Now it's a constexpr function too.
[class.copy.ctor] (emphasis mine)
12 A copy/move constructor that is defaulted and not defined as
deleted is implicitly defined when it is odr-used ([basic.def.odr]),
when it is needed for constant evaluation ([expr.const]), or when it
is explicitly defaulted after its first declaration. [ Note: The
copy/move constructor is implicitly defined even if the implementation
elided its odr-use ([basic.def.odr], [class.temporary]). — end note ]
If the implicitly-defined constructor would satisfy the requirements
of a constexpr constructor ([dcl.constexpr]), the implicitly-defined
constructor is constexpr.
Does the evaluation of the copy c'tor run afoul of any of the points in [expr.const]/4? It does not. It doesn't perform an lvalue to rvalue conversion on any of the argument's members (there are none to perform the conversion on). It doesn't use its reference parameter in any way that will require said reference to be usable in a constant expression. So we indeed get a valid constant expression, albeit a non-intuitive one.
We can verify the above by just adding a member to S.
struct S
{
int a = 1;
constexpr S(int a) {}
};
Now the copy c'tor is trying to access an object that is not usable in a constant expression as part of its evaluation (via said reference). So indeed, compilers will complain.

Should an explicitly defaulted constexpr ctor allow non-constexpr initialization

I just stumbled upon the following differences between GCC and Clang concerning an explicitly defaulted constexpr ctor and some inheritance...
template <typename T>
struct A {
constexpr A() = default;
T v;
};
struct B : A<int> {
constexpr B() = default;
};
GCC immediately rejects the code while Clang allows to instantiate non-constexpr versions of both types. My guess is that Clang is probably correct, but I'm not 100% certain...
The problem boils down to:
is a constexpr constructor that default-initializes
some non-static data member of builtin type valid,
if it is not used?
tl;dr:
For a non-template constructor,
no, it is invalid to leave any non-static data members uninitialized.
For a template constructor, yes,
it is valid to have some (but not all, no diagnostic required)
instantiated template specializations
for which the instantiated constructor does not meet the requirements
of a constexpr constructor.
In this case, GCC is right, whereas Clang is wrong.
GCC gives the following error message which is very informative:
prog.cc:8:13: error: explicitly defaulted function 'constexpr B::B()' cannot be declared as 'constexpr' because the implicit declaration is not 'constexpr':
8 | constexpr B() = default;
| ^
prog.cc:3:13: note: defaulted constructor calls non-'constexpr' 'A<T>::A() [with T = int]'
3 | constexpr A() = default;
| ^
prog.cc:3:13: note: 'A<T>::A() [with T = int]' is not usable as a 'constexpr' function because:
prog.cc:4:5: note: defaulted default constructor does not initialize 'int A<int>::v'
4 | T v;
| ^
live demo
Note that the error is raised on the constructor of B,
instead of that of A,
whose constructor is merely "not usable as a constexpr function
because [the] defaulted default constructor
does not initialize int A<int>::v."
Per [dcl.constexpr]/4:
The definition of a constexpr constructor shall satisfy the following
requirements:
the class shall not have any virtual base classes;
each of the parameter types shall be a literal type.
In addition, either its function-body shall be = delete, or it
shall satisfy the following requirements:
[...]
every non-variant non-static data member and base class subobject
shall be initialized ([class.base.init]);
[...]
Here, v is of type int, and is not initialized.
Therefore, it seems that the constructor of A
cannot be declared constexpr.
However, [dcl.constructor]/6 says:
If the instantiated template specialization of a constexpr function
template or member function of a class template would fail to satisfy
the requirements for a constexpr function or constexpr constructor,
that specialization is still a constexpr function or constexpr
constructor, even though a call to such a function cannot appear in a
constant expression. If no specialization of the template would
satisfy the requirements for a constexpr function or constexpr
constructor when considered as a non-template function or constructor,
the template is ill-formed, no diagnostic required.
Therefore, the constructor of A that is declared constexpr
is actually valid,
even when instantiated for T = int!
The problem is the constructor of B.
B is an ordinary class (as opposed to a class template),
and for its constructor to be (merely) declared constexpr,
A<int> must have a constexpr constructor,
which is not the case.
Therefore, this code should be rejected, as GCC does.
(Note that both compilers reject initialization of such a type,
for example:
A a{};
B b{};
The above code is rejected by both compilers.)
As mentioned in a comment,
initialize A::v and GCC (and the standard) will be happy.

Constexpr conditions for constructor

On this site, it is specified that:
"A constexpr function must satisfy the following requirements:
[...]
there exists at least one set of argument values such that an invocation of the function could be an evaluated subexpression of a core constant expression (for constructors, use in a constant initializer is sufficient) (since C++14). No diagnostic is required for a violation of this bullet."
What is the meaning of the bolded statement?
Looking at the linked defect report
struct X {
std::unique_ptr<int> p;
constexpr X() { }
};
Before C++14, this would be ill-formed due to [dcl.constexpr]
For a constexpr constructor, if no argument values exist such that after function invocation substitution, every constructor call and full-expression in the mem-initializers would be a constant expression (including conversions), the program is ill-formed; no diagnostic required.
Which mandates that there exists some argument (in this case, only the empty set) that can create a constant expression for the invocation of X::X, as in
constexpr X x; // must be valid before C++14
Since std::unique_ptr isn't a literal type, it has a non-trivial destructor, this is impossible. Yet the defect report proposed that constexpr constructors should still be well-formed in such cases due to this kind of use case
X x; // not constexpr, but initialization should be constant
Hence the rewording
For a constexpr function or constexpr constructor that is neither defaulted nor a template, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression, or, for a constructor, a constant initializer for some object , the program is ill-formed, no diagnostic required.
Translated, it means: a constexpr constructor is well-formed as long as it is a constexpr function, and its member initializations are also constexpr functions, even if the type itself can never be constexpr.

Trying to understand [expr.const] in C++14

Where in the C++14 Standard, does it prohibit the declaration of object a below?
class A{ int i = 1; public: A():i{1}{} };
int main()
{
constexpr A a{};
}
See live example
Note that I highlighted the word declaration, because I don't think bullet points (2.7.2) or (2.7.3), in §5.19[expr.const]p2 is an answer for the question.
[dcl.constexpr]p9:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall have literal type and shall be initialized. If it is initialized by a constructor call, that call shall be a constant expression (5.19). [...]
The error you're getting now is because your type is not a literal type. Your type is not a literal type because it does have a custom constructor, but doesn't have any constexpr constructor. The wording in the error message is rather clear about the exact requirements.
If you add a constexpr constructor (but not the default constructor), the error message changes:
class A{ int i = 1; public: A():i{1}{} constexpr A(int){} };
int main()
{
constexpr A a{};
}
Now the error message becomes
error: call to non-constexpr function ‘A::A()’
constexpr A a{};
This is the second part I bolded: it's not the initialiser that has to be a constant expression. You're right, your initialiser isn't an expression at all. It's the constructor call that must be a constant expression, and although it isn't expressed explicitly in the source code, it is an expression nonetheless. This is covered in [expr.const] rather clearly:
an invocation of a function other than a constexpr constructor for a literal class, a constexpr function, or an implicit invocation of a trivial destructor (12.4) [...]
to which you already refer in your question.
Well, your default constructor is not constexpr. Therefore, you cannot create a default constructed constexpr object.