-Wglobal-constructors warns on constexpr default constructor evaluated at compile-time?

Hello Clang experts,

it seems that -Wglobal-constructors throws a warning on a global using a constexpr default constructor on classes that don’t strictly have POD members only. Given constexpr + no params implies all members are either POD or constexpr default constructible themselves, shouldn’t Clang be able to avoid the global constructor in all cases?

e.g.:

struct Foo {
public:
constexpr Foo() = default;

std::unique_ptr my_int_; → std::unique_ptr’s default constructor is constexpr
};

Foo f; → error: declaration requires a global destructor [-Werror,-Wglobal-constructors]

void Init() {
printf(“%d”, *f.my_int_);
}

Is this expected?

Thanks,
Gab

Just in case somebody missed that, note that despite the switch name the error message is about the destructor.
Which makes sense for a unique_ptr which can be constexpr constructible but is clearly not trivially destructible. I just would have expected that to show up in -Wexit-time-destructors not in -Wglobal-constructors.

By glancing at the code in Sema::FinalizeVarWithDestructor it seems the logic for finalizers is:

  • always warn on -Wexit-time-destructors for any sort of non trivial dtor
  • warn also on -Wglobal-constructors but only for non-static-local cases.

I can’t see a case that would generate a warn_exit_time_destructor without a warn_global_destructor.
What is the intended semantic of -Wglobal-constructors? Would it be possible to make it only warn about constructors and leave destructors only to -Wexit-time-destructors?

it seems that -Wglobal-constructors throws a warning on a global using a
constexpr default constructor on classes that don't strictly have POD
members only. Given constexpr + no params implies all members are either POD
or constexpr default constructible themselves, shouldn't Clang be able to
avoid the global constructor in all cases?

The warning is about the *de*structor:

Aaaaah! I had definitely missed that this was about the dedestructor…!

I would indeed expect, like Primiano, that -Wglobal-constructors would only warn about constructors (one can decide to mitigate global destructors another way, e.g. by invoking _exit() before end of main()).

The point of -Wglobal-constructors is to avoid code which runs at process startup. Due to the way the C++ ABI works, a global destructor involves emitting a call to __cxa_atexit which runs at startup, so we warn.

-Eli

Well, there are (at least) four distinct problems here:

1) Code running at process startup (a performance problem)
2) Code running at process termination (a performance problem)
3) Global variables with non-constant initialization resulting in buggy
program startup (called the "initialization order fiasco" by some)
4) Global variables with non-trivial destruction resulting in buggy
program shutdown, particularly in multithreaded code (if you have
non-trivial global dtors, multiple threads, and you call exit, you
typically have a bug)

It makes sense to request warnings about #3 without requesting the more
general warnings about #1; as such, ignoring destructors in the
-Wglobal-constructors warning would seem reasonable to me. (That allows all
four problems to be detected by some combination of the two warning flags.)

Yes, precisely.

A program can decide to mitigate against 2/4 by atomically exiting with _exit() before the end of main() and hence truly only want to be warned by 1/3.

I'm not sure (3) is a problem this warning solves well; there are a lot of classes with constructors which aren't constexpr, but don't have relevant side-effects (for example, std::vector). But I guess we could introduce a new warning flag. (We probably don't want to change the meaning of the current warning flag for the sake of compatibility with existing build systems.)

On a side-note, it would be nice if the following worked to suppress global destructors:

struct A { ~A(); };
template<typename T> union NoDestroy {
T t;
constexpr NoDestroy():t{} {}
constexpr ~NoDestroy() {}
};
NoDestroy<A> x;

-Eli

I would indeed expect, like Primiano, that -Wglobal-constructors would
only warn about *con*structors (one can decide to mitigate global
*de*structors another way, e.g. by invoking _exit() before end of main()).

The point of -Wglobal-constructors is to avoid code which runs at
process startup. Due to the way the C++ ABI works, a global destructor
involves emitting a call to __cxa_atexit which runs at startup, so we warn.

Well, there are (at least) four distinct problems here:

1) Code running at process startup (a performance problem)
2) Code running at process termination (a performance problem)
3) Global variables with non-constant initialization resulting in buggy
program startup (called the "initialization order fiasco" by some)
4) Global variables with non-trivial destruction resulting in buggy
program shutdown, particularly in multithreaded code (if you have
non-trivial global dtors, multiple threads, and you call exit, you
typically have a bug)

It makes sense to request warnings about #3 without requesting the more
general warnings about #1; as such, ignoring destructors in the
-Wglobal-constructors warning would seem reasonable to me. (That allows all
four problems to be detected by some combination of the two warning flags.)

Yes, precisely.

A program can decide to mitigate against 2/4 by atomically exiting with
_exit() before the end of main() and hence truly only want to be warned by
1/3.

I'm not sure (3) is a problem this warning solves well; there are a lot of
classes with constructors which aren't constexpr, but don't have relevant
side-effects (for example, std::vector).

Yes, but it depends on how you try to solve (3). If you allow
dynamically-initialized globals, but only if they don't reference other
dynamically-initialized globals, you're right that this is pretty far from
ideal. But that approach is fraught with peril, since even
innocuous-seeming changes become wrong if you happen to be changing code
that's reachable from a dynamically-initialized global. A more reliable
(but obviously more restrictlve) approach is to avoid
dynamically-initialized globals altogether, which this warning does help
with :slight_smile: Given the ongoing expansion of the powers of constexpr evaluation,
the more restrictive approach is likely to become appropriate for more
projects over time.

But I guess we could introduce a new warning flag. (We probably don't
want to change the meaning of the current warning flag for the sake of
compatibility with existing build systems.)

On a side-note, it would be nice if the following worked to suppress
global destructors:

struct A { ~A(); };
template<typename T> union NoDestroy {
  T t;
  constexpr NoDestroy():t{} {}
  constexpr ~NoDestroy() {}
};
NoDestroy<A> x;

This already suppresses the global dtor when compiled with optimization
enabled (and the 'constexpr' on the destructor removed): we optimize out
__cxa_atexit calls registering empty functions, IIRC. Support for constexpr
destructors is working its way through the C++ committee right now, and
that should behave as you expect on the above example (removing the global
dtor).