Let's say, for reasons of my own, I want a class to have a non-static reference member. I would think that this type should be easily optimized out of most code that uses it. Therefore, I assert its triviality in a unit test.
Clang and GCC agree that the class is trivial, but MSVC disagrees. Who is right, and why, according to the standard?
#include <type_traits>
struct example {
int& r;
};
// Clang and GCC let this pass
// MSVC fires this assertion
static_assert(
std::is_trivial<example>::value,
"example is not trivial"
);
According to C++17 [class]/6, for a class to be trivial, among other requirements, it has to have at least one non-deleted default constructor. The example class's default constructor is deleted, so example is not a trivial class.
Before C++17, the situation is somewhat less clear. It was required for a trivial class to have a trivial default constructor, and it was not clear whether a deleted default constructor qualifies as trivial. Richard Smith asked in CWG 1928 whether special member functions that are defaulted and implicitly deleted are trivial. The Committee's response was:
CWG feels that the triviality of a deleted function should be irrelevant. Any cases in which the triviality of a deleted function is observable should be amended to remove that dependency.
Subsequently, the resolution of CWG 1496 resolved this issue in the case of trivial classes: it no longer matters whether a deleted default constructor qualifies as trivial, because in C++17, the definition says that all default constructors (in case there are multiple) must be either trivial or deleted and at least one must be non-deleted.
It seems that GCC and Clang may have been considering deleted default constructors as trivial, and haven't been updated with the C++17 definition of "trivial class". You should file bug reports.
Related
Do the C++ "Core Guidelines" prevent (or, at least, strongly discourage) a class with a std::string member from having a default constructor? I ask because of an issue I've noticed when using the MSVC Code Analysis tool (in Visual Studio 2019) in its most 'severe' setting; the issue is the "C26455" warning, as discussed in this document:
The C++ Core Guidelines suggest that default constructors shouldn't do
anything that can throw. If the default constructor is allowed to
throw, operations such as move and swap will also throw which is
undesirable because move and swap should always succeed. Parameterized
constructors may throw.
The problem is illustrated in the following, trivial code sample (when compiled using the C++17 Standard – see the "edit" note, below):
#include <string>
class Person {
private:
std::string my_name;
public:
// C26455: Default constructor may not throw. Declare it 'noexcept'...
Person() : my_name{ "Anonymous" } {}
// // C26447: The function is declared 'noexcept' but calls function 'allocator<char> >()' which may throw exceptions...
// Person() noexcept : my_name{ "Anonymous" } {}
};
Now, if I heed the generated C26455 warning (as shown in the comment), and add the noexcept specifier to the default constructor (that commented-out in the code sample above), I get a different warning, indicating that my constructor calls a routine in the standard library that may throw:
warning C26447: The function is declared 'noexcept' but calls function
'allocator >()' which may throw exceptions (f.6).
Note that I get the same warning if I remove the initializer list from the constructor and add that default value initializer to the declaration of the my_name member (as std::string my_name{ "Anonymous" };). However, all warnings disappear if I don't explicitly initialize the my_name member – which means I rely on the default constructor for std::string to create an empty object of unspecified capacity.
Presumably, another option would be to provide my own, non-throwing allocator, and pass that as the second argument to the my_name constructor – but this seems, to me, to be a somewhat over-complex solution to what must be a very common use case. (Similar situations would arise when using other standard library container objects as class members that require initialization involving allocation.)
Is there a simple resolution to this issue? Or should I just ignore/disable those code analysis warnings and core guidelines?
EDIT: Note that, when compiling to the C++20 Standard, the C26447 warning goes away when adding the noexcept specifier to the default constructor. I don't understand what C++20 offers that makes this happen; if I did, then maybe that would give a hint as to how to handle the situation for code compiled using the C++17 Standard (as I would prefer).
In my earlier (now deleted) answer, I speculated that it was that the string literal operator (""s) has a constexpr version from C++20 onwards, but it appears that this cannot, in itself, provide the explanation.
I've been trying to understand the rationale for std::nullopt_t to not be allowed to be DefaultConstructible in C++17 (where it was introduced) and beyond, and stepped over some compiler variance confusion in the process.
Consider the following spec-violating (it is DefaultConstructible) implementation of nullopt_t:
struct nullopt_t {
explicit constexpr nullopt_t() = default;
};
which is an aggregate in C++11 and C++14 (no user-provided ctors), but which is not an aggregate in C++17 (explicit ctor) and C++20 (user-declared ctor).
Now consider the following example:
struct S {
constexpr S() {}
S(S const&) {}
S& operator=(S const&) { return *this; } // #1
S& operator=(nullopt_t) { return *this; } // #2
};
int main() {
S s{};
s = {}; // GCC error: ambiguous overload for 'operator=' (#1 and #2)
}
This is rejected by GCC (various versions, say v11.0) throughout C++11 to C++20, but is accepted by both Clang (say v12.0) and MSVC (v19.28) throughout C++11 to C++20.
DEMO
My initial assumptions were that the program:
is ill-formed in C++11 and C++14, as nullopt_t (as above) is an aggregate, whereas it
is well-formed in C++17 and C++20, as it is no longer an aggregate, meaning that its explicit default constructor should prohibit copy-list-init of a temporary nullopt_t object as needed for the copy assignment operator at #2 to be viable,
but none of the compilers agree in full with this theory, some I'm probably missing something.
What compiler is correct here (if any), and how do we explain it by relevant standard sections (and DR:s, if relevant)?
Why is nullopt_t required to be DefaultConstructible in the first place?
The spec requirement that nullopt_t shall not be DefaultConstructible is arguably, in retrospect, a mistake based on some LWG and CWG confusion around tag types, and the resolution of this confusion which came only after std::optional was brought in from the Library Fundamentals TS Components.
First of all, the current (C++17, C++20) spec of nullopt_t, [optional.nullopt]/2, requires [emphasis mine]:
Type nullopt_t shall not have a default constructor or an initializer-list constructor, and shall not be an aggregate.
and its main use is described in the previous section, [optional.nullopt]/1:
[...] In particular, optional<T> has a constructor with nullopt_t as a single argument; this indicates that an optional object not containing a value shall be constructed.
Now, P0032R3 (Homogeneous interface for variant, any and optional), one of the papers which was part of introducing std::optional, has a discussion around nullopt_t, tag types in general, and the DefaultConstructible requirement [emphasis mine]:
No default constructible
While adapting optional<T> to the new in_place_t type we found
that we cannot anymore use in_place_t{}. The authors don't consider
this a big limitation as the user can use in_place instead. It needs
to be noted that this is in line with the behavior of nullopt_t as
nullopt_t{} fails as no default constructible. However nullptr_t{}
seems to be well formed.
Not assignable from {}
After a deeper analysis we found also that the old in_place_t
supported in_place_t t = {};. The authors don't consider this a big limitation as we don't expect that a lot of users could use this and the user can use
in_place instead.
in_place_t t;
t = in_place;
It needs to be noted that this is in line with the behavior of
nullopt_t as the following compile fails.
nullopt_t t = {}; // compile fails
However nullptr_t seems to be support it.
nullptr_t t = {}; // compile pass
To re-enforce this design, there is an pending issue 2510-Tag types should not be DefaultConstructible Core issue 2510.
And indeed, the initial proposed resolution of LWG Core Issue 2510 was to require all tag types to not be DefaultConstructible [emphasis mine]:
(LWG) 2510. Tag types should not be DefaultConstructible
[...]
Previous resolution [SUPERSEDED]:
[...] Add a new paragraph after 20.2 [utility]/2 (following the header synopsis):
-?- Type piecewise_construct_t shall not have a default constructor. It shall be a literal type. Constant piecewise_construct shall be initialized with an argument of literal type.
This resolution was superseded, however, as there were overlap with CWG Core Issue 1518, which was eventually resolved in a way that did not require tag types to not be DefaultConstructible, as explicit would suffice [emphasis mine]:
(CWG) 1518. Explicit default constructors and copy-list-initialization
[...]
Additional note, October, 2015:
It has been suggested that the resolution of issue 1630 went too far in allowing use of explicit constructors for default initialization, and that default initialization should be considered to model copy initialization instead. The resolution of this issue would provide an opportunity to adjust that.
Proposed resolution (October, 2015):
Change 12.2.2.4 [over.match.ctor] paragraph 1 as follows:
[...] For direct-initialization or default-initialization, the candidate functions are all the constructors of the class of the object being initialized. [...]
as long as explicit also implied that the type was not an aggregate, which in turn was the final resolution of LWG Core Issue 2510 (based on the final resolution of CWG Core Issue 1518)
(LWG) 2510. Tag types should not be DefaultConstructible
[...]
Proposed resolution:
[...] In 20.2 [utility]/2, change the header synopsis:
// 20.3.5, pair piecewise construction
struct piecewise_construct_t { explicit piecewise_construct_t() = default; };
constexpr piecewise_construct_t piecewise_construct{};
[...]
These latter changes, however, were not brought into the proposal for std::optional, arguably an oversight, and I would like to claim that nullopt_t need not be required to not be DefaultConstructible, only, like other tag types, that it should have a user-declared explicit constructor, which bans it from a candidate for empty-braces copy-list-init both by it not being an aggregate and by the only candidate constructor being explicit.
Which compiler is right and wrong here?
Given the LWG 2510, CWG 1518 (and other) confusion, let's focus on C++17 and beyond. In this case, GCC is arguably wrong to reject the program, whereas Clang and MSVC are correct to accept it.
Why?
Because the S& operator=(nullopt_t) assignment operator is not viable for the assignment s = {};, as the empty braces {} would require either aggregate initialization or copy-list-initialization to create a nullopt_t (temporary) object. nullopt_t, however (by the idiomatic tag implementation: my implementation above), as per as per P0398R0 (which resolves CWG Core Issue 1518), is neither an aggregate nor does its default constructor participate in copy-list-initialization (from empty braces).
This likely falls under the following GCC bug report:
Bug 54835 - (C++11)(DR 1518) Explicit default constructors not respected during copy-list-initialization
which was listed as SUSPENDED in 2015-06-15, before the change in the resolution of CWG Core Issue 1630 ("resolution of issue 1630 went too far"). The ticket is now re-opened based on a ping from this Q&A.
Consider the following code:
#include <memory>
#include <vector>
class A
{
private:
std::vector<std::unique_ptr<int>> _vals;
};
int main()
{
A a;
//A a2(a);
return 0;
}
Compiler A compiles this without issue unless I uncomment out the line A a2(a); at which point it complains about the copy constructor for std::unique_ptr being deleted, and therefore I can't copy construct A. Compiler B, however, makes that complaint even if I leave that line commented out. That is, compiler A only generates an implicitly defined copy constructor when I actually try to use it, whereas compiler B does so unconditionally. Which one is correct? Note that if I were to have used std::unique_ptr<int> _vals; instead of std::vector<std::unique_ptr<int>> _vals; both compilers correctly implicitly delete both copy constructor and assignment operator (std::unique_ptr has a explicitly deleted copy constructor, while std::vector does not).
(Note: Getting the code to compile in compiler B is easy enough - just explicitly delete the copy constructor and assignment operator, and it works correctly. That isn't the point of the question; it is to understand the correct behavior.)
From [class.copy.ctor]/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.
A's copy constructor is defaulted, so it's implicitly defined only when it is odr-used. A a2(a); is just such an odr-use - so it's that statement that would trigger its definition, that would make the program ill-formed. Until the copy constructor is odr-used, it should not be defined.
Compiler B is wrong to reject the program.
Note: My answer is based on your comment:
[...] it's only on Windows, and only when I explicitly list class A as a DLL export (via, e.g., class __declspec(dllexport) A) that this happens. [...]
On MSDN we can learn that declaring a class dllexport makes all members exported and required a definition for all of them. I suspect the compiler generates the definitions for all non-deleted functions in order to comply with this rule.
As you can read here, std::is_copy_constructible<std::vector<std::unique_ptr<int>>>::value is actually true and I would expect the supposed mechanism (that defines the copy constructor in your case for export purposes) checks the value of this trait (or uses a similar mechanism) instead of actually checking whether it would compile. That would explain why the bahviour is correct when you use unique_ptr<T> instead of vector<unique_ptr<T>>.
The issue is thus, that std::vector actually defines the copy constructor even when it wouldn't compile.
Imho, a is_copy_constructible check is sufficient because at the point where your dllexport happens you cannot know whether the implicit function will be odr-used at the place where you use dllimport (possibly even another project). Thus, I wouldn't think of it as a bug in compiler B.
While I can't confirm this behavior (I do not have access to Windows compiler, and OP claims the bug happens with icc on Windows platform), taking the question at it's face value, the answer is - compiler B has a gross bug.
In particular, implicitly-declared copy constructor is defined as deleted, when ...
T has non-static data members that cannot be copied (have deleted,
inaccessible, or ambiguous copy constructors);
https://en.cppreference.com/w/cpp/language/copy_constructor
Thus, conforming compiler must semantically generate deleted copy-constructor, and successfully compile the program since such constructor never called.
Code looks like:
struct Foo {
Foo(const char *);
};
Foo::Foo(const char *str = 0)
{
}
VS 2013 and gcc 4.8.0 accept such code,
while clang 3.3 reject such code with:
error: addition of default argument on redeclaration makes this constructor a default constructor
who is right from standard (C++03 and C++11) point of view?
Note:
I like clang's choice too, but I going to report bug to gcc and visual studio,
and if this is not correct from standard point of view, this helps to
convince compiler's developers to fix this issue.
GCC
I described issue here: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=58194
But no luck, they suspend bug fixing untill draft become standard.
This has been discussed on the Clang mailinglist and has been submitted as a Defect Report Core Issue 1344.
From the mailinglist discussion:
The idea is that the presence of certain special members affects core
properties of a class type, like whether it's POD or trivially
copyable. Deciding these properties should not require whole-program
knowledge; it's important for us to be able to deduce them just from
the class definition. The really problematic case is turning a
"normal" constructor into a copy or move constructor by adding default
arguments, but IIRC introducing a default constructor was also
problematic.
The fix is that you should put the default argument in the initial
declaration of the constructor.
This was last discussed by WG21 at the Bloomington meeting. Notes from
there:
"Consensus: Make this ill-formed as suggested in the write-up. Core
issue 1344. Priority 0, Doug drafting."
So CWG has agreed (in principle) that this should be ill-formed.
TL;DR Clang is right whenever the defect gets fixed (not sure if that can officially only happen with C++14, or if such Committee decisions can also be done retroactively on C++11)
I would say CLANG is right. The standard says (12.1.5 for the both old and new versions of the standard):
A default constructor for a class X is a constructor of class X that can be called without an argument
Adding the default value to the only argument of the constructor definitely makes it possible to call it without arguments, thus making it a default one. Also, 8.3.6 says (emphasis mine):
A default argument expression shall be specified only in the
parameter-declaration-clause
of a function declaration <...>
You have a declaration and a definition. In your declaration you do not have a default value, while in your definition you have a default value. In fact the signature of the declaration is very similar to the signature of the definition, but not the same. I believe that strictness is a good idea, so I believe it is better to enforce that the declaration is the same as the definition.
The following code does not compile in GCC 4.7.2 or Clang 3.2:
#include <vector>
#include <functional>
int main()
{
std::vector<std::function<void()>> a;
std::vector<std::function<void()>> b{a};
}
The issue is that the compiler will try to create b using an initializer_list, when clearly it should just be calling the copy constructor. However this seems to be desired behavior because the standard says that initializer_list constructors should take precedence.
This code would work fine for other std::vector, but for a std::function the compiler can't know whether you want the initializer_list constructor or another one.
It doesn't seem like there is a way around it, and if that is the case then you can never use uniform initialization in templated code. Which would be a giant shame.
Visual Studio (2012 November CTP) on the other hand doesn't complain about this. But the initializer_list support is not very good in there at the moment, so it might be a bug.
This is LWG 2132 which is not yet a Defect Report but there's clear consensus (and implementation experience) to fix it. The standard says that std::function's constructor will accept any type, so because an initializer-list constructor is always preferred to other constructors if it's viable, your code tries to construct a vector from an std::initializer_list<std::function<void()>> with a single element initialized from the object a. That then causes an error because although you can construct a std::function<void()> from a the resulting object isn't callable.
In other words the issue is that std::function has an unconstrained template constructor allowing conversion from any type. That causes a problem in your case because initializer-list constructors are preferred to other constructors if viable, and the unconstrained function constructor means it's always possible to create an initializer_list<function<void()>> from any type so an initializer-list constructor is always viable.
The proposed resolution to 2132 prevents constructing a std::function from a non-callable type, so the initializer-list constructor isn't viable and the vector copy constructor is called instead. I implemented that resolution for GCC 4.8, and it's already implemented in Clang's libc++ library too.
I can't see any reason why this shouldn't compile and both gcc (version 4.8.0 20121111) and clang (version 3.3 (trunk 171007)) compile the code. That said, "uniform initialization" is far from uniform: There are definitely cases where you can't use braces when invoking a constructor.