llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
27.84k stars 11.47k forks source link

constexpr allows uninitialized variables during default initialization #26110

Open tomilov opened 8 years ago

tomilov commented 8 years ago
Bugzilla Link 25736
Version 3.7
OS Linux
CC @john-brawn-arm,@tomilov

Extended Description

Given a code:

    #include <type_traits>

    struct A { int i; };

    template< typename type >
    struct C { constexpr C() = default; type a; };

    struct V { V() = default; C< A > d; };

    constexpr bool test()
    {
        V v;
        static_assert(std::is_trivially_default_constructible< V >::value, "!");
        return (v.d.a.i == 0);
    }

    static_assert(test(), "!");

It compiles without errors by clang. Compilation by g++ failed with error:

main.cpp: In function 'constexpr bool test()':
main.cpp:12:11: error: uninitialized variable 'v' in 'constexpr' function
         V v;
           ^
main.cpp:8:12: note: 'struct V' has no user-provided default constructor
     struct V { V() = default; C< A > d; };
            ^
main.cpp:8:16: note: constructor is not user-provided because it is explicitly defaulted in the class body
     struct V { V() = default; C< A > d; };
                ^
main.cpp:3:20: note: and the implicitly-defined constructor does not initialize 'int A::i'
     struct A { int i; };
                    ^
main.cpp: At global scope:
main.cpp:17:5: error: non-constant condition for static assertion
     static_assert(test(), "!");
     ^
main.cpp:17:23: error: 'constexpr bool test()' called in a constant expression
     static_assert(test(), "!");
                       ^

If I remove templates and substitute A directly, then clang sees uninitialized during default construction variables. If I remove constexpr specifier of C::C() default constructor, then clang also sees wrong things.

john-brawn-arm commented 3 years ago

A similar case where neither a template version nor a non-template version gives an error:

  struct A {
    union {
      int j;
    };
    constexpr A() {}
  };
  constexpr A a;
  int fn1() {
    return a.j;
  }

  class B {};
  template <class> struct C {
    union {
      int j;
    };
    constexpr C() {}
  };
  constexpr C<B> c;
  int fn2() {
    return c.j;
  }

Here the generated code for both fn1 and fn2 returns undef. We do get a warning though on the non-template version (unless using -std=c++20):

tmp.cpp:5:13: warning: constexpr constructor that does not initialize all members is a C++20 extension [-Wc++20-extensions]
  constexpr A() {}
            ^
tmp.cpp:2:3: note: member not initialized by constructor
  union {
  ^

It's http://open-std.org/JTC1/SC22/WG21/docs/papers/2019/p1331r2.pdf that defines that extension, but there (in the section "Avoiding undefined behaviour") it says that we should be emitting an error on the use of the uninitialized value.

john-brawn-arm commented 3 years ago

With current trunk the above example gives the error

tmp.cpp:17:15: error: static_assert expression is not an integral constant expression
static_assert(test(), "!");
              ^~~~~~
tmp.cpp:14:11: note: read of uninitialized object is not allowed in a constant expression
  return (v.d.a.i == 0);
          ^
tmp.cpp:17:15: note: in call to 'test()'
static_assert(test(), "!");
              ^

though there are still differences compared to a non-template version, in that in a non-template version the error is on the definition of the class, whereas for a template version we get an error only on some kinds of use. If we look at

  struct A { int i; };

  template< typename type >
  struct C1 { constexpr C1() = default; type a; };
  struct V1 { V1() = default; C1<A> d; };

  int test1()
  {
    V1 v;
    return v.d.a.i;
  }

  struct C2 { constexpr C2() = default; A a; };
  struct V2 { V2() = default; C2 d; };

  int test2()
  {
    V2 v;
    return v.d.a.i;
  }

the definition of the non-template C2 gets an error:

tmp.cpp:13:13: error: defaulted definition of default constructor is not constexpr
struct C2 { constexpr C2() = default; A a; };
            ^

but there's no such error for the templated C1. If we compile just the first half of this example there are no errors and the result is a function that returns undef (at -O1).