is_nothrow_default_constructible with a noexcept(false) default constructor

std::is_assignable
is_default_constructible
c++ type traits
constructible c++
c type support
c type transformations
type_traits noexcept
c++ reflection cppreference

I was trying to static_assert a few type traits to ensure that a custom type had the expected noexcept guarantees when I stumbled upon a strange behaviour. The reduced snippet above illustrates the issue:

struct DefaultOnly
{
    constexpr DefaultOnly() noexcept(false) {};
};

static_assert(std::is_nothrow_default_constructible_v<DefaultOnly>);

For this simple type GCC 8 passes the static_assert while Clang 7 fails it. I don't know which compiler is right. Is this a bug in one of the compilers or is the standard definition of nothrow default constructible flexible enough so that both compilers produce valid but different results based on their interpretation of the standard?

This issue is not directly related to a constructor with noexcept specification, but how compilers do treat constant expressions when noexcept is in play.

If you declare the constructor as no constexpr, then both compilers works as expected:

struct DefaultOnly
{
    DefaultOnly() noexcept(false) {};
};
static_assert(std::is_nothrow_default_constructible_v<DefaultOnly>);

Back to C++11, constant expressions weren't sensible to noexcept specification but that went through changes up to C++17. up to now constexpr functions are affected by noexcept specification.

Clang works as expected.

The following code will show the same behavior as yours:

constexpr int foo() noexcept(false) { return 0;}
static_assert(noexcept(foo()));

As reference, this is an extract of the GCC-87603 report:

CWG 1129 (which ended up in C++11) added a special case to noexcept for constant expressions, so that:

constexpr void f() {} static_assert(noexcept(f()));

CWG 1351 (which ended up in C++14) changed the wording significantly, but the special case remained, in a different form.

P0003R5 (which ended up in C++17) changed the wording again, but the special case was removed (by accident), so now:

constexpr void f() {} static_assert(!noexcept(f()));

According to Richard Smith in LLVM 15481, CWG discussed this but decided to keep the behavior as-is. Currently, clang does the right thing for C++17 (and fails for C++14 and C++11, on purpose). g++, however, implemented the special case for C++11 already, but not the change for C++17. Currently, icc and msvc seem to behave like g++.

Also see GCC-86044 and the GCC-88453 is more specifically equivalent to your case.

std::is_default_constructible, std , std::is_nothrow_default_constructible provides the member constant value equal to true, otherwise value is false. checks if the destructor throws because it is effectively noexcept(T()). a non-trivial default ctor }; struct Ex2 { int n; Ex2() = default; // trivial and checks if a type has a copy constructor In many implementations, is_nothrow_default_constructible also checks if the destructor throws because it is effectively noexcept (T ()). Same applies to is_trivially_default_constructible , which, in these implementations, also requires that the destructor is trivial: GCC bug 51452 LWG issue 2116 .

As of C++17, Clang is right. Until then, constexpr overrode the noexcept(false) because the noexcept operator always returned true for constant expressions.

85642 – [8/9 Regression] Wrong implicit exception-specification with , Add noexcept to constructor for copying disengaged payloads. Log: PR libstdc​++/85642 fix is_nothrow_default_constructible<optional<T>> Add missing noexcept (_Optional_payload): Add noexcept to default constructor. A list of complete types (or void, possibly cv-qualified, or an array of unknown bound) representing the argument types for the constructor form, in the same order as in the constructor. If omitted, it checks whether the type is nothrow default-constructible. Member types Inherited from integral_constant:

std::is_nothrow_constructible - is_nothrow_constructible, or an array of unknown bound) representing the argument types for the constructor form, If omitted, it checks whether the type is nothrow default-​constructible. <iostream> #include <type_traits> struct A { }; struct B { B(){} B( const A&) noexcept {} }; int is_nothrow_constructible: int(): true A(): true B(): false B(A): true  A default constructible class is a class that has a default constructor (either its implicit constructor or a custom defined one). The is_default_constructible class inherits from integral_constant as being either true_type or false_type, depending on whether T is default constructible.

Using noexcept, Previous guidelines for using noexcept were far too limiting. which is very useful in templates, or to express a may-throw: noexcept(false). T) may not be T​. Also, for constructors, the default initialization of sub-objects may go unnoticed. is_nothrow_constructible,; is_nothrow_default_constructible  Notes. In many implementations, is_nothrow_constructible also checks if the destructor throws because it is effectively noexcept (T (arg)).Same applies to is_trivially_constructible, which, in these implementations, also requires that the destructor is trivial: GCC bug 51452 LWG issue 2116.

std::is_default_constructible, std::is_trivially_default_constructible , std::is_default_constructible, std::is_trivially_default_constructible, std::​is_nothrow_default_constructible also checks if the destructor throws because it is effectively noexcept(T()). Ex1 is default-constructible? true Ex1 is trivially default-constructible? false Ex2 is checks if a type has a constructor for specific arguments Move constructors (and destructors) would be implicitly declared as noexcept and therefore compiler would statically check if move constructors (and destructors) were really not throwing exceptions. If for some reason you needed your move constructor or destructor to be able to throw exceptions, you would have to explicitly declare the function

C++ Templates: The Complete Guide, false cout << is nothrow default constructible voC 3 \n'; // false cout << is copy And because we define a default constructor without noexcept, it might throw. Exceptions. throw-expression. function-try-block. try/catch block. noexcept specifier (C++11) noexcept operator (C++11) Dynamic exception specification (until C++20) Specifies whether a function could throw exceptions. Contents.

Comments
  • So GCC folks aren't sure whether to get rid of the special case or not because it was removed accidentally. Interesting. Too bad we can't rely on it now ^^'