As we know, in C++20 a class that has a user-declared constructor is not aggregate.
Now, consider the following code:
#include <type_traits>
template <bool EnableCtor>
struct test {
template <bool Enable = EnableCtor, class = std::enable_if_t<Enable>>
test() {}
int a;
};
int main() {
test<false> t {.a = 0};
}
Neither GCC nor CLang compile this code. So, the class is not an aggregate, despite the fact that there is no instantiated constructor here.
Is the constructor template considered to be a "declared constructor"? What does the Standard say about such a situation?
The definition of aggregate has changed a lot, but the relevant part here has been pretty constant. The C++20 wording in [dcl.init.aggr] says:
An aggregate is an array or a class ([class]) with
no user-declared or inherited constructors ([class.ctor]),
no private or protected direct non-static data members ([class.access]),
no virtual functions ([class.virtual]), and
no virtual, private, or protected base classes ([class.mi]).
Note that it just says no declared constructors, period. Not something subtle about whether or not the constructor is viable for a particular specialization of a class template. Just none at all.
So given something like this:
template <bool B>
struct X {
int i;
X(int i) requires B;
};
X<false> still isn't an aggregate, even though its only declared constructor isn't viable for that specialization. Doesn't matter. Aggregate-ness is a property of declarations only.
Related
I have this test case:
struct A{ protected: A(){} };
struct B: A{};
struct C: A{ C(){} };
struct D: A{ D() = default; };
int main(){
(void)B{};
(void)C{};
(void)D{};
}
Both gcc and clang compile it in C++11 and C++14 mode. Both fail in C++17 mode:
$ clang++ -std=c++17 main.cpp
main.cpp:7:10: error: base class 'A' has protected default constructor
(void)B{};
^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
^
main.cpp:9:10: error: base class 'A' has protected default constructor
(void)D{};
^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
^
2 errors generated.
$ clang++ --version
clang version 6.0.0 (http://llvm.org/git/clang.git 96c9689f478d292390b76efcea35d87cbad3f44d) (http://llvm.org/git/llvm.git 360f53a441902d19ce27d070ad028005bc323e61)
Target: x86_64-unknown-linux-gnu
Thread model: posix
(clang compiled from master Branch 2017-12-05.)
$ g++ -std=c++17 main.cpp
main.cpp: In function 'int main()':
main.cpp:7:10: error: 'A::A()' is protected within this context
(void)B{};
^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
^
main.cpp:9:10: error: 'A::A()' is protected within this context
(void)D{};
^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
^
$ g++ --version
g++ (GCC) 8.0.0 20171201 (experimental)
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Is this change of behavior part of C++17 or is it a bug in both compilers?
The definition of aggregate changed since C++17.
Before C++17
no base classes
Since C++17
no virtual, private, or protected (since C++17) base classes
That means, for B and D, they're not aggregate type before C++17, then for B{} and D{}, value-initialization will be performed, then the defaulted default constructor will be called; which is fine, because the protected constructor of base class could be called by derived class's constructor.
Since C++17, B and D become aggregate type (because they have only public base class, and note that for class D, the explicitly defaulted default constructor is allowed for aggregate type since C++11), then for B{} and D{}, aggregate-initialization will be performed,
Each direct public base, (since C++17) array element, or non-static class member, in order of array subscript/appearance in the class definition, is copy-initialized from the corresponding clause of the initializer list.
If the number of initializer clauses is less than the number of members and bases (since C++17) or initializer list is completely empty, the remaining members and bases (since C++17) are initialized by their default initializers, if provided in the class definition, and otherwise (since C++14) by empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates). If a member of a reference type is one of these remaining members, the program is ill-formed.
That means the base class subobject will be value-initialized directly, the constructor of B and D are bypassed; but the default constructor of A is protected, then the code fails. (Note that A is not aggregate type because it has a user-provided constructor.)
BTW: C (with a user-provided constructor) is not an aggregate type before and after C++17, so it's fine for both cases.
In C++17, rules about aggregates has changed.
For example, you can do this in C++17 now:
struct A { int a; };
struct B { B(int){} };
struct C : A {};
struct D : B {};
int main() {
(void) C{2};
(void) D{1};
}
Note that we're not inheriting constructor. In C++17, C and D are now aggregates even if they have base classes.
With {}, aggregate initialization kicks in, and sending no parameters will be interpreted the same as calling the parent's default constructor from the outside.
For example, aggregate initialization can be disabled by changing the class D to this:
struct B { protected: B(){} };
struct D : B {
int b;
private:
int c;
};
int main() {
(void) D{}; // works!
}
This is because aggregate initialization don't apply when having members with different access specifiers.
The reason why with = default works is because it's not a user provided constructor. More information at this question.
In an attempt to answer another question, I came up with a scheme to force children of a CRTP base class to accept a particular type as a parameter in their constructors: make the parameter type's constructor private, assign the CRTP base class as a friend, and declare the parameter type as a parameter for the base class constructor as well.
However, when I tried to demonstrate that this scheme provided the desired protections via access violations, I found that even though the parameter type's constructor was private, the child class was able to construct it:
template <typename T>
class SingletonBase {
protected: class P { friend class SingletonBase<T>; P() = default; };
public:
SingletonBase(P) {}
};
class Logger: public SingletonBase<Logger> {
using BASE = SingletonBase<Logger>;
public:
Logger() : BASE{P{}} {} // WHY NO ACCESS VIOLATION?
};
This compiles without error, even though I'd expect an access violation. Why?
Does “friending” the base class in CRTP inheritance affect the child class as well?
No, of course not. Friendship is not inherited. To illustrate the issue,
Firstly, P::P() is a defaulted default constructor, it's a trivial default constructor.
Secondly, P{} is value initialization (since C++11),
(emphasis mine)
2) if T is a class type with a default constructor that is neither user-provided nor deleted (that is, it may be a class with an implicitly-defined or defaulted default constructor), the object is zero-initialized and then it is default-initialized if it has a non-trivial default constructor;
Note it'll be only zero initialized here, not default initializated. The private default constructor of P won't be invoked at all.
If T is an non-union class type, all base classes and non-static data members are zero-initialized, and all padding is initialized to zero bits. The constructors, if any, are ignored.
If you change it to default initialization explicitly, you'll get the access violation error.
Logger() : BASE{P()} {} // error: calling a private constructor of class 'SingletonBase<Logger>::P
// ~~
A simplified demonstration
class X { X() = default; };
int main()
{
X x1{}; // fine
X x2; // error: calling a private constructor of class 'X'
}
LIVE
Solution
You can provide a user-defined default constructor, which is a non-trivial constructor, to change the behavior of value-initialization.
template <typename T>
class SingletonBase {
protected:
class P {
friend class SingletonBase<T>;
P() {} // user-defined default constructor
};
public:
SingletonBase(P) {}
};
class Logger: public SingletonBase<Logger> {
using BASE = SingletonBase<Logger>;
public:
Logger() : BASE{P{}} {} // error: calling a private constructor of class 'SingletonBase<Logger>::P'
};
What you have done has nothing to do with your friend statement!
If you remove your friend the code compiles also fine!
That is because a default constructor for an empty class is public:
From C++11 standard:
If there is no user-declared constructor for class X, a constructor having no parameters is implicitly declared as defaulted. An implicitly-declared default constructor is an inline public member of its class.
If you have no default constructor like this:
template <typename T>
class SingletonBase
{
protected:
class P
{
friend class SingletonBase<T>;
P(int){ }
};
public:
SingletonBase(P) {}
};
class Logger: public SingletonBase<Logger>
{
using BASE = SingletonBase<Logger>;
public:
Logger() : BASE(P{1}) {} // WHY NO ACCESS VIOLATION?
};
You will get the "access" violation and you see that your friend did not work!:
main.cpp: In constructor 'Logger::Logger()':
main.cpp:10:17: error: 'SingletonBase<T>::P::P(int) [with T = Logger]' is private
P(int){ }
^
main.cpp:22:28: error: within this context
Logger() : BASE(P{1}) {} // WHY NO ACCESS VIOLATION?
I got a struct B that is derived from a struct A.
struct A{
int a;
};
struct B : public A{
int b;
};
Is there a straight way to initialize an object of type B without providing a constructor, let's say using initializer-list?
Some more insight:
I got two structs that I use to pass data between threads; the second one holds the same data as the first one, with the addition of some synchronization variable. I could make the first struct a data member of the second one, or just duplicate the declaration of data members in the second struct, to easily use the initializer-list; but I think that in this particular application it would be logically more correct that the second struct extends the first one.
There isn't an extremely concise solution, but there is a solution at least:
#include <type_traits>
#include <utility>
struct B : A
{
int b;
template <typename ...Args,
typename = typename std::enable_if<
std::is_constructible<A, Args&&...>::value>
B(int x, Args &&... args)
: b(x), A(std::forward<Args>(args)...)
{ }
};
This solution isn't exactly short, but it is general. The new constructor for B exists only in those specializations which make sense, thanks to the enable-if SFINAE, so B is precisely as constructible as it can be.
There is one more danger here that I didn't address, namely how explicit the new constructor should be. Ideally it should be as explicit as the matching A constructor, but that's a bit hard to detect programatically (as done in N4064 for pairs and tuples).
You can't use aggregate initialization for B, because it's not an aggregate, as per [dcl.init.aggr]/1:
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no private or
protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).
Update: Kerrek SB provided a good solution using template constructor, but if you like, you can add quite straightforward non-template constructor for B:
struct B : public A{
int b;
B(const A& a_, int b_) : A(a_), b(b_) {}
};
And use it with one extra pair of braces:
B b {{3}, 5};
I'm trying to declare a member name which is constructor of base class as the following:
#include <iostream>
class A{ };
class B: public A
{
using A::A; //error: ‘A::A’ names constructor
};
int main()
{
}
Where is it specified that constructor cannot be accepted by using declaration? I'm looking for a corresponding quote from the Standard.
Where is it specified that constructor cannot be accepted by using declaration?
Nowhere, because it can. See 12 Special Member Functions:
12.9 Inheriting constructors [class.inhctor]
A using-declaration (7.3.3) that names a constructor implicitly declares a set of
inheriting constructors. The candidate set of inherited constructors from the class X named in the using-declaration consists of actual constructors and notional constructors that result from the transformation of defaulted parameters as follows:
— all non-template constructors of X, and
— for each non-template constructor of X that has at least one parameter with a default argument, the set of constructors that results from omitting any ellipsis parameter specification and successively omitting parameters with a default argument from the end of the parameter-type-list, and
— all constructor templates of X, and
— for each constructor template of X that has at least one parameter with a default argument, the set of constructor templates that results from omitting any ellipsis parameter specification and successively omitting parameters with a default argument from the end of the parameter-type-list.
....
Here's an example:
struct A
{
explicit A(int) {}
};
struct B: A
{
using A::A;
};
int main()
{
B b{42};
}
Should a compiler deduce the template argument when the destructor of a class is called ?
The following piece of code :
#include <iostream>
template <typename T>
class A{
};
int main(){
A<int> * a = new A<int>();
a->~A();
}
compiles fine on gcc (g++ 4.3.4) but fails on XLC++ with
line 30.5: 1540-0210 (S) "A" is not a base class of
"A<int>"
Which of the two behavior is expect from a standard compliant compiler ?
The C++03 standard (I doubt that the C++11 standard will be any different) has the following paragraph (C++03 14.3/5 [temp.arg]):
An explicit destructor call (12.4) for an object that has a type that is a class template specialization may explicitly specify the template-arguments. [Example:
template<class T> struct A {
~A();
};
void f(A<int>* p, A<int>* q) {
p->A<int>::~A(); // OK: destructor call
q->A<int>::~A<int>(); // OK: destructor call
};
--end example]
Clause 12.4/12 [class.dtor] describes the explicit destructor call in terms of a call to a regular member function and has an example showing the explicit destructor call with both qualified and unqualified versions for the destructor's type.
To me, this clearly indicates that the intent of the standard is that each of
A<int> * a = new A<int>();
a->~A();
a->~A<int>();
a->A<int>::~A();
a->A<int>::~A<int>();
should be valid.
The fact that the first two are not mentioned in the example in 14.4/12 should not affect this, as examples are not normative.