Consider the following code segment:
#include <iostream>
using std::cout;
using std::endl;
class A
{
public:
//constexpr A (){i = 0;}
constexpr A ():i(0){}
void show (void){cout << i << endl; return;}
private:
int i;
};
class B
{
public:
constexpr B(A a){this->a = a;}
//constexpr B(A a):a(a){}
void show (void){a.show(); return;}
private:
A a;
};
int main (void)
{
A a;
B b(a);
b.show();
return (0);
}
Inside the definition of class A, if the current constructor definition is replaced by the definition commented out:
//constexpr A (){i = 0;}
the following compilation error ensues (note that line numbers correspond to original code):
g++ -ggdb -std=c++17 -Wall -Werror=pedantic -Wextra -c code.cpp
code.cpp: In constructor ‘constexpr A::A()’:
code.cpp:8:30: error: member ‘A::i’ must be initialized by mem-initializer in ‘constexpr’ constructor
constexpr A (){i = 0;}
^
code.cpp:12:13: note: declared here
int i;
^
make: *** [makefile:20: code.o] Error 1
However, the code compiles perfectly with either definition for the constructor of class B (the current as well as the definition commented out in the source code reproduced.)
I have looked at the following pages with the objective of understanding what is going on here:
constexpr specifier (since C++11)
Constant expressions
I must admit that I am not able to figure out why the member initializer list is mandated in the case of the constructor for A, and not in the case of B.
Appreciate your thoughts.
A has a default constructor (which is constexpr by the way). The relevant requirements for a constexpr constructor that you are citing are as follows:
for the constructor of a class or struct, ... every non-variant
non-static data member must be initialized.
The only requirement that it is "initialized". Not "explicitly initialized". A default constructor will meet the initialization requirement. A has a default constructor. So your B's a class member gets initialized by its default constructor, meeting this requirement, so you don't have to explicitly initialize it in B's constructor's initialization list.
On the other hand, your garden variety int does not have a default constructor. For that reason, A's constexpr constructor must initialize it explicitly.
Related
This works: (A)
class Foo {
public:
const bool b;
constexpr ~Foo() = default;
constexpr Foo(const bool b) : b(b) {};
};
class Bar {
public:
static constexpr Foo tru { true };//Foo is complete type
};
This fails to compile: (B)
class Bar {
public:
class Foo {
public:
const bool b;
constexpr ~Foo() = default;
constexpr Foo(const bool b) : b(b) {};
};
static constexpr Foo tru { true };//undefined constructor 'Foo' cannot be used
};
error:
$ clang++ --std=c++20 -D_POSIX_C_SOURCE=200112L -fPIC -g -Werror -Wall LiteralStruct.cpp -o LiteralStruct
LiteralStruct.cpp:9:24: error: constexpr variable 'tru' must be initialized by a constant expression
static constexpr Foo tru { true };
^~~~~~~~~~~~
LiteralStruct.cpp:9:24: note: undefined constructor 'Foo' cannot be used in a constant expression
LiteralStruct.cpp:7:15: note: declared here
constexpr Foo(const bool b) : b(b) {};
^
1 error generated.
This also fails to compile, but gives a good reason: (C)
class Foo {
public:
const bool b;
constexpr ~Foo() = default;
constexpr Foo(const bool b) : b(b) {};
static constexpr Foo tru { true };//Foo is NOT a complete type
};
error:
$ clang++ --std=c++20 -D_POSIX_C_SOURCE=200112L -fPIC -g -Werror -Wall LiteralStruct.cpp -o LiteralStruct
LiteralStruct.cpp:6:24: error: constexpr variable cannot have non-literal type 'const Foo'
static constexpr Foo tru { true };
^
LiteralStruct.cpp:6:24: note: incomplete type 'const Foo' is not a literal type
LiteralStruct.cpp:1:7: note: definition of 'Foo' is not complete until the closing '}'
class Foo {
version:
clang version 10.0.0-4ubuntu1
Target: x86_64-pc-linux-gnu
C failing makes sense and has a good error message. B feels like it should work, Foo and all it's contents should be complete and defined at that point in the file. Basically my question is: do I report a clang bug that B should work, or a feature request for a better error message? If Foo is truly not complete by virtue of being a member of an incomplete type, then I should think the error message should be similar to that of C.
Edit:
I just upgraded clang to the bleeding edge (16.0.0-++20221021052626+7dd2f4bc009d-1~exp1~20221021172738.418) and got the same result.
The problem with (B) is distinct from the one with (C). In (B) the completeness of Foo is not in question. Foo is complete as soon as the closing } of its definition is reached and since the member declaration of tru is placed after that, Foo is complete for its purpose.
There is also no problem in (B) with Bar being incomplete at the declaration of tru. While that's true, incompleteness does not prevent looking up members which were declared prior, like the nested class Foo.
The problem is, as the error message is pointing out, whether or not the constructor Foo::Foo(bool) is defined at the point of the declaration of tru. This is an important question, because tru is initialized in that declaration using the constructor in question and it is marked constexpr, requiring that the initialization be a constant expression. Calling a (constexpr) function in a constant expression is only allowed if the function is defined (not only declared) prior to the expression.
Consider for example
class Bar {
public:
class Foo {
public:
const bool b;
constexpr ~Foo() = default;
constexpr Foo(const bool b);
};
static constexpr Foo tru { true };
};
constexpr Bar::Foo::Foo(const bool b) : b(b) {};
You will get the same or a similar error message here. In this case it is more obvious what the issue is. When static constexpr Foo tru { true }; is reached and the compiler tries to evaluate the (compile-time constant) value of Foo, it hasn't seen the definition of the constructor yet, so it can't know how to determine the value of tru.
Now in your example (B) it seems that Bar::Foo::Foo(bool) is defined before it is used in the constant expression for tru and I think if one follows the current standard by exact wording, then this is true. However, there is a complication which changes this in practice and in the probable intent of the standard:
The body of a function defined inside a class is special in that it is a so-called complete-class context. In such a context it is possible for name lookup to find not only preceding declarations as is normally the case in C++, but also declarations for all members of the class (and enclosing classes), irregardless of whether they are declared only later.
So for example the following is allowed:
class Bar {
public:
class Foo {
public:
const bool b;
~Foo() = default;
Foo(const bool b) : b(X) {};
};
constexpr static bool X = false;
};
Although X is not declared yet when Foo::Foo(bool) is defined and uses X, the compiler has to accept it and figure out that X is the static member declared at the end.
In order to achieve this lookup behavior, the compiler practically must rewrite the code to
class Bar {
public:
class Foo {
public:
const bool b;
~Foo() = default;
Foo(const bool b);
};
constexpr static bool X = false;
};
inline Bar::Foo(const bool b) : b(X) {};
Now "normal" lookup rules can find X as expected.
But if we apply this rewriting to your example (B) we get my first example in this answer and as we determined it cannot work with the constant expression evaluation. So in practice you can't use a member function of the same class (including nested or enclosing classes) in a constant expression evaluation for a static data member inside the class definition itself.
That the current standard wording doesn't describe this behavior properly is an issue with the standard, not the compiler. My impression is that Clang is implementing it as intended. While it may sometimes be possible to consider the constructor defined where it is lexically placed for the purpose of constant expression evaluation if e.g. all names used in its definition can already be found at that point, in general it is impossible, because you could create infinite recursive type dependencies this way.
The first thing to note is that a complete-class context of a nested class is also a complete-class context of any enclosing class, if the nested class is defined within the member-specification of the enclosing class as per mem.general#7.
This combined with the fact that the body of the constructor Bar::Foo::Foo(const bool) is a complete-class context means that the names appearing inside the body of Bar::Foo::Foo(const bool) will be lookup up as if the definition of that ctor is placed after the enclosing class' }.
This in turn implies that at the point where you have the definition/declaration of the constexpr data member tru, the constructor Bar::Foo::Foo(const bool) is not yet defined. But this violates expr.const#5 which states:
5. An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:
5.2 an invocation of an undefined constexpr function;
(emphasis mine)
This is exactly what the error of one of the compiler says says:
error: constexpr variable 'tru' must be initialized by a constant expression
The following code, as far as I can tell, correctly initializes the variables of the derived class B:
#include <utility>
struct A {
int i;
};
struct B : A {
int j;
explicit B(A&& a) : A(std::move(a)), j{i} { }
};
int main()
{
A a{3};
B b(std::move(a));
return 0;
}
Running cppcheck with --enable=all gives the warning:
[test.cpp:9]: (warning) Member variable 'A::i' is not initialized in
the constructor. Maybe it should be initialized directly in the class
A?
Is there a reason for this (false, I think) warning?
Yes, this looks like a false positive. Base class subobjects are initialized before direct member subobjects and A(std::move(a)) will use the implicit move constructor which initializes this->i with a.i, so this->i will be initialized before the initialization of this->j is performed (which reads this->i).
The argument given to the constructor in main is also completely initialized via aggregate initialization, so a.i's value will not be indeterminate either.
struct SS {int a; int s;};
int main ()
{
vector<SS> v;
v.push_back(SS{1, 2});
}
The code can be compiled without any error. However, when the struct is initialized in class, I got compilation error. Can anyone explain it?
struct SS {int a = 0; int s = 2;};
Error:
In function ‘int main()’:
error: no matching function for call to ‘SS::SS(<brace-enclosed initializer list>)’
v.push_back(SS{1, 2});
^
note: candidates are:
note: constexpr SS::SS()
struct SS {int a = 0; int s = 2;};
^
note: candidate expects 0 arguments, 2 provided
note: constexpr SS::SS(const SS&)
note: candidate expects 1 argument, 2 provided
note: constexpr SS::SS(SS&&)
note: candidate expects 1 argument, 2 provided
In C++11, when you use non static data member initialization at the point of declaration like you do here:
struct SS {int a = 0; int s = 2;};
you make the class a non-aggregate. This means you can no longer initialize an instance like this:
SS s{1,2};
To make this initialization syntax work for a non-aggregate, you would have to add a two-parameter constructor:
struct SS
{
SS(int a, int s) : a(a), s(s) {}
int a = 0;
int s = 2;
};
This restriction has been lifted in C++14.
Note that you may want to add a default constructor for the class. The presence of a user-provided constructor inhibits the compiler generated default one.
See related reading here.
Use of a default member initializer renders the class/struct a non-aggregate:
§ 8.5.1 Aggregates
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no brace-or-equal-initializers for non-static data members (9.2), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).
Semantics differ for aggregates and non-aggregates:
Aggregates (e.g., arrays and structs):
Initialize members/elements beginning-to-end.
Non-aggregates:
Invoke a constructor.
v.push_back(SS{1, 2}); // Error, it tries to call SS constructor
Which means you need a constructor now:
struct SS
{
SS(int a, int s) : a(a), s(s)
{
}
int a = 0;
int s = 2;
};
I had the same problem. In my case, I had two structs that both had a few constructors, including copy constructors, inheriting from an abstract parent.
When the advice above didn't help, I finally realized I needed to remove explicit specifier from the copy constructors and that removed the error.
Thought I would share in case another poor soul spends as long finding this mistake as I just did.
I have a bunch of template classes, based on an enumeration type. Here is the source code:
#include <iostream>
// An enum type
enum class ENUMR : unsigned char {
SYSTEM1,
SYSTEM2,
UNKNOWN
};
// template class; will later be specialized based on ENUMR enumerators
template<ENUMR S>
class System
{};
// specialized System class, for enumerator SYSTEM1
template<>
class System<ENUMR::SYSTEM1>
{
private:
static constexpr char identifier { 'G' };
};
// An observation class recorded from a certain System instance
template<ENUMR Sys,short int Freq>
class Obs {
public:
Obs():
m_system {System<Sys> {} }, // does not work
m_frequency {Freq}
{};
//{
// m_system = System<Sys> {}; // works
//}
private:
System<Sys> m_system;
short int m_frequency;
};
// dummy code to test the template classes
int main ()
{
System<ENUMR::SYSTEM1> s1;
System<ENUMR::UNKNOWN> s2;
System<ENUMR::SYSTEM1> s3 (System<ENUMR::SYSTEM1>);
Obs<ENUMR::SYSTEM1, 1> obs;
std::cout <<"\n";
return 0;
}
Initialization of the member (template) variable Obs::m_system produces a compile-time error, when performed as m_system {System<Sys> {} }. However, when the same variable is initialized as m_system = System<Sys> {}; the source codes gets compiled (the respective lines in the source code above are commented).
Does that mean that the assignment operator works, but the copy constructor fails? If so, why?
When compiled against the gcc version 4.8.3 i get the following error message:
test.cpp: In instantiation of ‘Obs<Sys, Freq>::Obs() [with ENUMR Sys = (ENUMR)0u; short int Freq = 1]’:
test.cpp:43:28: required from here
test.cpp:25:22: error: too many initializers for ‘System<(ENUMR)0u>’
m_frequency {Freq} {};
If you change
m_system{ System<Sys>{} },
m_frequency{ Freq }
to
m_system( System<Sys>{} ),
m_frequency( Freq )
it will compile. Note however, that the first initializer creates a default constructed temporary, and then uses the copy constructor to initialize the member. Since you want m_system to be default constructed, that initializer can be omitted.
System<ENUMR::SYSTEM1> is an aggregate, so the mem-initializer m_system { System<Sys>{} } performs aggregate-initialization. Since System<ENUMR::SYSTEM1> has no corresponding member - indeed it has no members at all - for the corresponding initializer System<Sys>{}, you get the error indicating there are too many initializers.
If System<ENUMR::SYSTEM1> were not an aggregate, the effect of that mem-initializer would be to direct-list-initialize m_system from a value-initialized temporary of the same type. Since the move/copy constructors are defaulted, you could achieve the equivalent effect for both the aggregate and non-aggregate case by directly value-initializing m_system with an empty brace initializer: m_system {}. (DEMO)
struct SS {int a; int s;};
int main ()
{
vector<SS> v;
v.push_back(SS{1, 2});
}
The code can be compiled without any error. However, when the struct is initialized in class, I got compilation error. Can anyone explain it?
struct SS {int a = 0; int s = 2;};
Error:
In function ‘int main()’:
error: no matching function for call to ‘SS::SS(<brace-enclosed initializer list>)’
v.push_back(SS{1, 2});
^
note: candidates are:
note: constexpr SS::SS()
struct SS {int a = 0; int s = 2;};
^
note: candidate expects 0 arguments, 2 provided
note: constexpr SS::SS(const SS&)
note: candidate expects 1 argument, 2 provided
note: constexpr SS::SS(SS&&)
note: candidate expects 1 argument, 2 provided
In C++11, when you use non static data member initialization at the point of declaration like you do here:
struct SS {int a = 0; int s = 2;};
you make the class a non-aggregate. This means you can no longer initialize an instance like this:
SS s{1,2};
To make this initialization syntax work for a non-aggregate, you would have to add a two-parameter constructor:
struct SS
{
SS(int a, int s) : a(a), s(s) {}
int a = 0;
int s = 2;
};
This restriction has been lifted in C++14.
Note that you may want to add a default constructor for the class. The presence of a user-provided constructor inhibits the compiler generated default one.
See related reading here.
Use of a default member initializer renders the class/struct a non-aggregate:
§ 8.5.1 Aggregates
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no brace-or-equal-initializers for non-static data members (9.2), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).
Semantics differ for aggregates and non-aggregates:
Aggregates (e.g., arrays and structs):
Initialize members/elements beginning-to-end.
Non-aggregates:
Invoke a constructor.
v.push_back(SS{1, 2}); // Error, it tries to call SS constructor
Which means you need a constructor now:
struct SS
{
SS(int a, int s) : a(a), s(s)
{
}
int a = 0;
int s = 2;
};
I had the same problem. In my case, I had two structs that both had a few constructors, including copy constructors, inheriting from an abstract parent.
When the advice above didn't help, I finally realized I needed to remove explicit specifier from the copy constructors and that removed the error.
Thought I would share in case another poor soul spends as long finding this mistake as I just did.