llvm / llvm-project

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

Aggregate initialization of struct containing zero-sized array disagrees with GCC #35489

Open Quuxplusone opened 6 years ago

Quuxplusone commented 6 years ago
Bugzilla Link 36141
Version trunk
OS All
CC @DougGregor,@zygoloid

Extended Description

Clang believes that an aggregate consisting of a zero-sized array and a 1-sized array is NOT initializable with just one initializer in the curly braces, whereas GCC believes that it IS.

prog.cc:15:16: error: initializer for aggregate with no elements requires explicit braces
    auto x = A{convertible_to_anything{}};
               ^

It would be convenient if Clang matched GCC's behavior for zero-sized arrays. The existing behavior is definitely not a "bug bug", as zero-sized arrays are non-standard and I'm not aware that Clang has promised to mimic GCC in this area; but the discrepancy between the two compilers in this area is awkward for testing.

#include <cstdio>
#include <type_traits>
#include <utility>

struct convertible_to_anything { template<class T> constexpr operator T&() const noexcept; };
template<class T, class, class> struct is_n_constructible_impl : std::false_type {};
template<class T, size_t... Is> struct is_n_constructible_impl<T, std::index_sequence<Is...>, decltype(void(T{(void(Is), convertible_to_anything{})...}))> : std::true_type {};

template<size_t N, class T> struct is_n_constructible : is_n_constructible_impl<T, std::make_index_sequence<N>, void> {};

struct A { int a[0]; int b[1]; };

int main() {
    static_assert(is_n_constructible<0, A>::value);
    if (is_n_constructible<1, A>::value) {
        puts("This is GCC");
    } else {
        puts("This is Clang");
    }
    static_assert(not is_n_constructible<2, A>::value);
    static_assert(not is_n_constructible<3, A>::value);
}
Quuxplusone commented 6 years ago

@​rsmith: I have not asked GCC, and don't plan to (although anyone else should feel free to do so).

My original test case (before reducing) made GCC's behavior much more "apparently intuitively correct":

template<int K> struct AA { int a[K/2]; int b[K - K/2]; };
template<int K> struct BB { int a[K - K/2]; int b[K/2]; };

static_assert(is_n_constructible<0, AA<0>>::value == true);
static_assert(is_n_constructible<0, BB<0>>::value == true);
static_assert(is_n_constructible<1, AA<1>>::value == ONLY_ON_GCC);
static_assert(is_n_constructible<1, BB<1>>::value == true);
static_assert(is_n_constructible<2, AA<2>>::value == true);
static_assert(is_n_constructible<2, BB<2>>::value == true);
static_assert(is_n_constructible<3, AA<3>>::value == true);
static_assert(is_n_constructible<3, BB<3>>::value == true);
static_assert(is_n_constructible<4, AA<4>>::value == true);
static_assert(is_n_constructible<4, BB<4>>::value == true);
// ...
ec04fc15-fa35-46f2-80e1-5d271f2ef708 commented 6 years ago

Clang is enforcing [dcl.init.aggr]p12 (http://eel.is/c++draft/dcl.init.aggr#12):

If an aggregate class C contains a subaggregate element e with no elements, the initializer-clause for e shall not be omitted from an initializer-list for an object of type C unless the initializer-clauses for all elements of C following e are also omitted.

GCC's behavior here does not seem reasonable nor consistent with the base language to me. Have you asked the GCC folks whether they would consider this to be a bug and might fix it? Example of the inconsistency:

struct A {};
struct B { int n; };
struct X { A a; B b; };

struct Y { int a[0]; int b[1]; };

X{convertible_to_anything{}} // constructs 'a' member via conversion function
Y{convertible_to_anything{}} // constructs 'b' member via conversion function!