Uniform vs legacy initialisation producing different compilation results - c++

I alighted on this while permuting with a trivial piece of code:
struct Base0 {};
struct Base1 {};
template<typename... Ts>
struct Derived: Ts... {};
int main() {
Derived<Base0, Base1> d0 {Base0{}, Base1{}}; // OK
Derived<Base0, Base1> d1 (Base0{}, Base1{}); // ERROR
}
I thought both d0 and d1 should have resulted in a compilation error since I can't see how Derived without any matching ctor takes ctor arguments as passed and flags d0's compilation as fine.
There's probably something obvious I'm missing. What is it about the uniform initialisation that's making it pass ? Is it aggregate initialisation or something ? What's happening with the temporaries passed to the ctor ?
Using C++17 online compiler here
Edit
As asked, I'm providing a copy-paste of the spew-out:
main.cpp: In function ‘int main()’:
main.cpp:9:47: error: no matching function for call to ‘Derived::Derived(Base0, Base1)’
Derived<Base0, Base1> d1 (Base0{}, Base1{}); // ERROR
^
main.cpp:5:8: note: candidate: constexpr Derived::Derived()
struct Derived: Ts... {};
^~~~~~~
main.cpp:5:8: note: candidate expects 0 arguments, 2 provided
main.cpp:5:8: note: candidate: constexpr Derived::Derived(const Derived&)
main.cpp:5:8: note: candidate expects 1 argument, 2 provided
main.cpp:5:8: note: candidate: constexpr Derived::Derived(Derived&&)
main.cpp:5:8: note: candidate expects 1 argument, 2 provided

Looks like this is a new C++17 feature of aggregate initialisation:
Each direct public base, (since C++17) array element, or non-static class member, in order of array subscript/appearance in the class definition, is copy-initialized from the corresponding clause of the initializer list.
It comes with the change that a class with bases may now be an aggregate (as long as they are not virtual, private, or protected… though they don't even need to be aggregates! 😲).
Your failing case does not use aggregate initialisation, but instead attempts a good old-fashioned constructor invocation. As you've identified, no such constructor exists.

Related

initializer_list constructor somehow excluded from std::variant constructor overload set

Help me solve this puzzle: In the following code I have an std::variant which forward declares a struct proxy which derives from this variant. This struct is only used because recursive using declarations are afaik not a thing in C++ (unfortunately). Anyway, I pull in all the base class constructors of the variant which define for each declared variant alternative T
template< class T >
constexpr variant( T&& t ) noexcept(/* see below */);
according to cppreference. I would assume that this means that a constructor for std::initializer_list<struct proxy> as type T is also defined. However, this doesn't seem to be the case. The following code results in an error:
#include <variant>
using val = std::variant<std::monostate, int, double, std::initializer_list<struct proxy>>;
struct proxy : val
{
using val::variant;
};
int main()
{
proxy some_obj = {1,2,3,2.5,{1,2}};
}
CompilerExplorer
Clang Error (because gcc doesn't go into much detail):
<source>:12:11: error: no matching constructor for initialization of 'proxy'
proxy some_obj = {1,2,3,2.5,{1,2}};
^ ~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/13.0.0/../../../../include/c++/13.0.0/variant:1434:2: note: candidate template ignored: could not match 'in_place_type_t<_Tp>' against 'int'
variant(in_place_type_t<_Tp>, initializer_list<_Up> __il,
^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/13.0.0/../../../../include/c++/13.0.0/variant:1455:2: note: candidate template ignored: could not match 'in_place_index_t<_Np>' against 'int'
variant(in_place_index_t<_Np>, initializer_list<_Up> __il,
^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/13.0.0/../../../../include/c++/13.0.0/variant:1424:2: note: candidate template ignored: could not match 'in_place_type_t<_Tp>' against 'int'
variant(in_place_type_t<_Tp>, _Args&&... __args)
^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/13.0.0/../../../../include/c++/13.0.0/variant:1444:2: note: candidate template ignored: could not match 'in_place_index_t<_Np>' against 'int'
variant(in_place_index_t<_Np>, _Args&&... __args)
^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/13.0.0/../../../../include/c++/13.0.0/variant:1401:7: note: candidate inherited constructor not viable: requires single argument '__rhs', but 5 arguments were provided
variant(const variant& __rhs) = default;
What I get from this is that the above mentioned constructor taking the variant alternatives T is not considered. Why?
Your proxy class does not have a declared constructor that accepts a std::initializer_list<proxy>. What it has, is a constructor template that accepts any type, T. But for that template to be chosen, the compiler has to deduce the type of T. The braced-init-list {1,2,3,2.5,{1,2}} does not have any inherent type though, so the compiler can't deduce the type T.
It's easy to think that a braced-init-list is a std::initializer_list, but that is not the case. There are some special cases where a std::initializer_list will be implicitly constructed from a braced-init-list, but deducing a template type parameter is not one of those cases.
You could explicitly construct a std::initializer_list<proxy>, i.e.
proxy some_obj = std::initializer_list<proxy>{1,2,3,2.5,std::initializer_list<proxy>{1,2}};
But keep in mind that std::initializer_list only holds pointers to its elements, and it does not extend their lifetimes. All of those proxy objects will go out of scope at the end of the full expression, and some_obj will immediately be holding a std::initializer_list full of dangling pointers. If you need a recursive type, you will almost certainly need to dynamically allocate the recursive children (and remember to clean them up, as well). std::initializer_list is not sufficient for this use case.

Overload resolution between two constructors from std::initializer_list

In following program, struct C has two constructors : one from std::initializer_list<A> and the other from std::initializer_list<B>. Then an object of the struct is created with C{{1}}:
#include <initializer_list>
struct A {
int i;
};
struct B {
constexpr explicit B(int) {}
};
struct C {
int v;
constexpr C(std::initializer_list<A>) : v(1) {}
constexpr C(std::initializer_list<B>) : v(2) {}
};
static_assert( C{{1}}.v == 1 );
Since only aggregate A can be implicitly constructed from int, one could expect that C(std::initializer_list<A>) is preferred and the program succeeds. And indeed it does in Clang.
However GCC complains:
error: call of overloaded 'C(<brace-enclosed initializer list>)' is ambiguous
note: candidate: 'constexpr C::C(std::initializer_list<B>)'
note: candidate: 'constexpr C::C(std::initializer_list<A>)'
and so does MSVC:
error C2440: '<function-style-cast>': cannot convert from 'initializer list' to 'C'
note: No constructor could take the source type, or constructor overload resolution was ambiguous
Demo: https://gcc.godbolt.org/z/joz91q4ed
Which compiler is correct here?
The wording could be clearer (which is unsurprising), but GCC and MSVC are correct here: the relevant rule ([over.ics.list]/7) checks only that
overload resolution […] chooses a single best constructor […] to perform the initialization of an object of type X from the argument initializer list
so the fact that the initialization of B from {1} would be ill-formed is irrelevant.
There are several such places where implicit conversion sequences are more liberal than actual initialization, causing certain cases to be ambiguous even though some of the possibilities wouldn’t actually work. If the programmer was confused and thought one of those near misses was actually a better match, it’s a feature that the ambiguity is reported.

In C++, when no constructor is declared for a class, what will happen if I construct an object with arguments?

I have the struct student and I did not declare a constructor. What will happen if I do the following?
struct student{
int assns, mt, finalExam;
float grade(){…}
}
student billy (60, 70, 80);
This answer is written according to the question heading, and not the body, as they seem to be gravely conflicting, hope the OP edits this.
You will encounter a error during compile time.
Code:
#include <iostream>
class test
{
int tt;
};
int main ()
{
test t1 (34);
}
Compiler Error:
In function 'int main()':
10:17: error: no matching function for call to 'test::test(int)' 10:17: note: candidates are:
2:7: note: test::test()
2:7: note: candidate expects 0 arguments, 1 provided
2:7: note: constexpr test::test(const test&)
2:7: note: no known conversion for argument 1 from 'int' to 'const test&'
2:7: note: constexpr test::test(test&&)
2:7: note: no known conversion for argument 1 from 'int' to 'test&&'
This happens as there is no constructor defined which takes a parameter. Without the ctor there is no meaning of class, as you can never initialize its data member, and how can you expect something to be constructed if the construction company itself is absent.
The compiler will throw error.

C++11 POD structure initializing error

I have confusing situation with simple code:
struct Item {
size_t span{};
};
int main() {
Item item{1}; // error is here
return 0;
}
While compiling this I have following error:
test.cpp: In function ‘int main()’:
test.cpp:8:13: error: no matching function for call to ‘Item::Item(<brace-enclosed initializer list>)’
Item i{1};
^
test.cpp:8:13: note: candidates are:
test.cpp:3:8: note: constexpr Item::Item()
struct Item {
^
test.cpp:3:8: note: candidate expects 0 arguments, 1 provided
test.cpp:3:8: note: constexpr Item::Item(const Item&)
test.cpp:3:8: note: no known conversion for argument 1 from ‘int’ to ‘const Item&’
test.cpp:3:8: note: constexpr Item::Item(Item&&)
test.cpp:3:8: note: no known conversion for argument 1 from ‘int’ to ‘Item&&’
Why g++ tries to find a ctor for initializer list in this case instead of simple C-style structure object creating?
If I remove {} from size_t span{} it compiles successfully.
It also happens if I change the line to size_t span = 0 so it seems to be some initialization in declaration issue which exists since c++11.
Usign Item item{1}; means you're doing list-initialisation (of item). List initialisation is defined as follows:
if the type is an aggregate, aggregate initialisation (what you refer to as "C-style struct object creating") happens
...
if the type is a class, constructors are considered
Your class has no constructors. It is also not a (C++11) aggregate, because it contains an initialiser for a non-static data member.
Note that this restriction (member initialisers) was lifted in C++14, so Item is a C++14 aggregate and your code, while not valid C++11, is valid C++14.

struct initialization with in-class member initializers

I'm having trouble initializing a struct that uses in-class initializers:
struct A
{
int a{};
int b{};
};
struct B
{
int a;
int b;
};
int main()
{
A a; // OK
B b{1, 2}; // OK
B b2; // OK, but b.a and b.b are undefined
A a2{1, 2}; // ERROR!
}
Here's the error I'm getting from gcc 4.7.2:
% g++ -std=c++11 test2.cc
test2.cc: In function ‘int main()’:
test2.cc:16:11: error: no matching function for call to ‘A::A(<brace-enclosed initializer list>)’
test2.cc:16:11: note: candidates are:
test2.cc:1:8: note: constexpr A::A()
test2.cc:1:8: note: candidate expects 0 arguments, 2 provided
test2.cc:1:8: note: constexpr A::A(const A&)
test2.cc:1:8: note: candidate expects 1 argument, 2 provided
test2.cc:1:8: note: constexpr A::A(A&&)
test2.cc:1:8: note: candidate expects 1 argument, 2 provided
Should this work according to the standard, or is this actually illegal? Am I abusing the use of in-class initializers? I thought the new syntax would make it so I wouldn't have to write a constructor just to do this initialization, but now it seems that I may have to resort to that old mechanism to avoid the possibility of an uninitialized structure.
You can only use braces if either
the brace content matches a constructor (not your case), or
the class is an aggregate and each brace element matches a class member.
However, a class is an aggregate if (C++11, 8.5.1/1):
it has no brace-or-equal-initializers for non-static members
which your class clearly has. So, you don't have an aggregate, either.
Either write suitable constructors, or remove the brace-or-equal-initializers.