In C++11, are the aggregates allowed to be copied with curly-braces syntax? I have the following code:
struct s
{
int x;
};
template<class T>
struct holder
{
template<class A>
holder(A&& x) : t{x} {}
T t;
};
Each one of the statements below works.
auto s1 = s{1};
auto s2(s1);
auto s3{s1}; ///NOTE : this works!
However, the second statement below raises the error cannot convert 's' to 'int' in initialization.
holder<s> h{5};
holder<s> h1{s{5}};
I am using gcc 4.8.2. Why do I get this error?
In C++11, when a type T is an aggregate type, initialisation using { ... } performs aggregate initialisation. Aggregate initialisation always initialises the members of T, not T itself.
Although this is exactly what the standard requires, this is unwanted, which is why in a future standard, the rule will likely be changed to make a special exception for initialisation from the same type. This is core language issue 1467.
Until that time, unfortunately, the error you are getting is entirely correct, and you will have to work around it.
First of all this code
struct s
{
int x;
};
template<class T>
struct holder
{
template<class A>
holder(A&& x) : t{x} {}
T t;
};
int main()
{
holder<s> h{5};
return 0;
}
is compiled successfuly.
The invalid statement is
holder<s> h1{s{5}};
The problem is that in fact you are trying to execute
s s1 = { s{5} };
However the compiler is unable to convert an object of type s to an object of type int (that to initialize s1.x) when it tries to make assignment
s.x = s{5};
Related
In the C language the name of a structured type S ist struct S.
In C++ one can also use struct S as typename instead of S as usual for
struct S {};
struct S s1; // also ok in C++
S s2; // normal way in C++
So, the assumption is, that using struct S or S as typename in C++ is a matter of taste ;-)
But in the following example there are places where struct S is not allowed:
struct S1 {
S1(int x = 0) : x{x} {}
int x{};
};
typedef S1 S2;
template<typename T>
auto foo(T a) {
// T::_; // T is deduced to `S` in all cases
return S1{a};
// return struct S1{a}; // NOK
}
int main() {
// foo(struct S1{i}); // NOK
S1 s1;
foo(s1);
struct S1 s2{2};
foo(s2);
foo(1);
// struct S2 s20; // NOK
S2 s21;
}
Can anyone explain to me what this unsymmetry is about?
It looks like struct S is not allowed where a ctor call as part of an expression is needed. But if a type S could also be written as struct S it should also be ok to use struct S{} as a ctor call.
Writing struct S1 s1; is allowed in C++ for one reason: compatibility with C. C allows you to have at the same time a function called S1, so struct S1 is an explicit disambiguation. C++ also allows this, for backwards compatibility only.
However, in contexts where backwards compatibility is not applicable, C++ does not. C doesn't have constructors, for instance.
There's one other point of confusion in the question:
typedef S1 S2;
struct S2 s2;
S2 is not a struct name but an alias name, and you can't prefix an alias name with struct.
the assumption is, that using struct S or S as typename in C++ is a matter of taste ;-)
The above assumption is wrong as there are exceptions to it(as also noted in your example) and the reason has more to do with C compatibility than a matter of taste.
The point is that there are rules for when we can/cannot use struct S1 as a replacement for just S1. And assuming that we can always use struct S1 as a replacement for S1 is wrong.
The operand of the return statement should be an expression but struct S1{a} is not an expression and hence cannot be used in the return statement:
//-----vvvvvvvvvvvv----> operand is not an expression
return struct S1{a};
An expression involving struct S1 would look something like:
return (struct S1)a;
There are also situation(s) when we need to use struct S1 and using just S1 won't work. A contrived example is given below:
struct S1
{
};
int S1()
{
std::cout<<"function called"<<std::endl;
return 5;
}
int main()
{
//S1 s; // ERROR: This won't work unless we use struct S1
struct S1 s2; //WORKS
}
class-key class or struct are used only in declarations. Constructors and destructors are named as functions using class-name.
For example in this declaration
struct A
{
A() = default;
~A() = default;
};
the constructor and destructor looks like function declarations that have no explicit return type.
Such a record
struct A
{
struct A() = default;
struct ~A() = default;
};
will be invalid. Encountering the keyword struct the compiler will consider these records as incorrect declarations.
I think that under the hood there is a desire to avoid a possible ambiguity. For example this construction
struct A()
looks like a function type that has no parameters and has the return type struct A. Or this record
struct A {}
looks like a structure definition.
Pay attention to that using an elaborated name also introduces a new type in the given scope. Consider this code snippet
struct A {};
int main()
{
struct A;
struct A { int x; };
}
The record struct A; in main introduces a new type in the block scope of main that hides the structure with the same name introduced in the global namespace..
The question is simple. Is it possible to construct such a type T, for which the following two variables declarations will produce different results?
T t1 = {};
T t2{};
I've been digging through the cppreference and the standard for more than an hour now, and I understood the following:
T t2{}; is a value initialization. No surprises here.
T t1 = {} is a list initialization with an empty braced-init-list.
But the last one is tricky since the "effects of list initialization" is an impressive... list. Which for classes, fundamental types and aggregates seems to boil down to value initialization. But I am not sure I haven't missed anything.
Maybe you can provide a context, in which the two declarations will have different effects?
UPD: Excellent answers about explicit constructors! Next level: is it possible that both statements compile, but have different effects on compile/run time?
If you consider a case in which one statement will compile, but the other will not compile as "different effects," then yes, here's a context:
#include <iostream>
class T {
public:
int data{ 0 };
explicit T() {
data = 0;
std::cout << "Default constructor" << std::endl;
}
};
int main()
{
T t1 = {};
T t2{};
return 0;
}
The line declaring/initializing t1 gives the following, with clang-cl:
error : chosen constructor is explicit in copy-initialization
The MSVC compiler also complains:
error C2512: 'T': no appropriate default constructor available
message : Constructor for class 'T' is declared 'explicit'
The difference is in explicit. I've managed to make msvc difference, but it looks like a compiler bug:
#include <iostream>
#include <initializer_list>
struct T
{
template<class... A>
T(A...) {std::cout << "1\n";}
explicit T() { std::cout << "2\n"; }
};
int main()
{
T t1 = {}; // 1
T t2{}; // 2
}
The following code compiles in clang 7+, but not in 5 & 6 (with c++17 and c++14).
The problem for clang 5 and 6 seems to be that the implicit copy ctor reads from the mutable member x.
Can anybody tell me, if the whole construction is standard-compliant (c++17), or if the program is ill-formed? Or was there a change in the standard regarding the implicit copy-ctor which might not be implemented in the earlier clang versions?
struct Foo {
int a;
mutable int x{};
constexpr Foo() : a(0) {}
//constexpr Foo(const Foo& other) : a(other.a) {} // <- with this line it works on Clang 5 & 6, too
};
struct FooFactory {
static constexpr auto create() {
auto f = Foo{};
return f;
}
};
int main() {
constexpr Foo f = FooFactory::create();
++f.x;
}
Live code here.
This is a well-formed C++17 program. A constexpr function can of course read (and write) non-constant variables:
constexpr int f(int i) {
int j=i;
++j;
return i+j; // neither is a constant expression
}
The rule is that anything examined in a constant expression must either be a constant or have its lifetime begun during the expression’s evaluation. In your case, the lifetime of create’s f.x plainly begins within the evaluation of the constant expression that is the initialization of main’s f. It is true, however, that no Foo object can be copied by a constant expression that does not also create that object, whether or not it is constexpr.
The only other candidate problem is if the copy constructor were not constexpr, but those requirements are very weak. The only relevant ones are that every (non-variant) member be initialized, which is certainly satisfied, and that it be usable in at least one constant expression, which has been demonstrated.
The code is ill-formed. For the reference, here is simplified code, which fails on all compilers:
struct Foo {
int a;
mutable int x{};
constexpr Foo() : a(0) {}
};
int main() {
constexpr Foo f;
constexpr Foo f1 = f;
}
As per [expr.const] 7.7,
A variable is usable in constant expressions after its initializing
declaration is encountered if
it is a constexpr variable,
or ... (3.5) a non-mutable
subobject or reference member of any of the above.
This disqualifies default copy constructors.
I wrote some code S s; ... s = {};, expecting it to end up the same as S s = {};. However it didn't. The following example reproduces the problem:
#include <iostream>
struct S
{
S(): a(5) { }
S(int t): a(t) {}
S &operator=(int t) { a = t; return *this; }
S &operator=(S const &t) = default;
int a;
};
int main()
{
S s = {};
S t;
t = {};
std::cout << s.a << '\n';
std::cout << t.a << '\n';
}
The output is:
5
0
My questions are:
Why is operator=(int) selected here, instead of "ambiguous" or the other one?
Is there a tidy workaround, without changing S?
My intent is s = S{}; . Writing s = {}; would be convenient if it worked. I'm currently using s = decltype(s){}; however I'd prefer to avoid repeating the type or the variable name.
Why is operator=(int) selected here, instead of "ambiguous" or the other one?
{} to int is the identity conversion ([over.ics.list]/9). {} to S is a user-defined conversion ([over.ics.list]/6) (technically, it's {} to const S&, and goes through [over.ics.list]/8 and [over.ics.ref] first before coming back to [over.ics.list]/6).
The first wins.
Is there a tidy workaround?
A variation of the trick std::experimental::optional pulls to make t = {} always make t empty.
The key is to make operator=(int) a template. If you want to accept int and only int, then it becomes
template<class Int, std::enable_if_t<std::is_same<Int, int>{}, int> = 0>
S& operator=(Int t) { a = t; return *this; }
Different constraints can be used if you want to enable conversions (you'd probably also want to take the argument by reference in that case).
The point is that by making the right operand's type a template parameter, you block t = {} from using this overload - because {} is a non-deduced context.
...without changing S?
Does template<class T> T default_constructed_instance_of(const T&) { return {}; } and then s = default_constructed_instance_of(s);count?
First of all, the case has nothing to do with the "int" version of the assignment operator, you can just delete it. You can actually delete the other assignment operator too as it will be generated by the compiler. IE this kind of type automatically receives copy/move constructors and the assignment operator. (ie they are not prohibited and you are just repeating what the compiler does automatically with explicit notation)
The first case
uses copy initialization:
S s = {}; // the default constructor is invoked
That is a post-construction copy assignment, yet compilers optimize such simple cases. You should use direction initialization instead:
S s{}; // the default constructor is invoked (as you have it)
Note, you can also write:
S s; // the default constructor is invoked if you have it
The second case
What you should write is direct initialization of the right hand side of the copy assignment:
t = S{};
This notation will invoke the default constructor (if there is one), or value initialization for the members (as long as the type is an aggregate). Here is the relevant info: http://en.cppreference.com/w/cpp/language/value_initialization
The following example initializing a std::array <char, N> member in a constructor using a string literal doesn't compile on GCC 4.8 but compiles using Clang 3.4.
#include <iostream>
#include <array>
struct A {
std::array<char, 4> x;
A(std::array<char, 4> arr) : x(arr) {}
};
int main() {
// works with Clang 3.4, error in GCC 4.8.
// It should be the equivalent of "A a ({'b','u','g','\0'});"
A a ({"bug"});
for (std::size_t i = 0; i < a.x.size(); ++i)
std::cout << a.x[i] << '\n';
return 0;
}
On first impression it looks like a GCC bug. I feel it should compile as we can initialize a std::array<char, N> directly with a string literal. For example:
std::array<char, 4> test = {"bug"}; //works
I would be interested to see what the Standard says about this.
Yes, your code is valid; this is a bug in gcc.
Here's a simpler program that demonstrates the bug (I've replaced std::array<char, 4> with S and got rid of A, as we can demonstrate the bug just in function return (this makes the analysis simpler, as we don't have to worry about constructor overloading):
struct S { char c[4]; };
S f() { return {"xxx"}; }
Here we have a destination object of type S that is copy-initialized (8.5p15) from the braced-init-list {"xxx"}, so the object is list-initialized (8.5p17b1). S is an aggregate (8.5.1p1) so aggregate initialization is performed (8.5.4p3b1). In aggregate initialization, the member c is copy-initialized from the corresponding initializer-clause "xxx" (8.5.1p2). We now return to 8.5p17 with destination object of type char[4] and initializer the string literal "xxx", so 8.5p17b3 refers us to 8.5.2 and the elements of the char array are initialized by the successive characters of the string (8.5.2p1).
Note that gcc is fine with the copy-initialization S s = {"xxx"}; while breaking on various forms of copy- and direct-initialization; argument passing (including to constructors), function return, and base- and member-initialization:
struct S { char c[4]; };
S f() { return {"xxx"}; }
void g(S) { g({"xxx"}); }
auto p = new S({"xxx"});
struct T { S s; T(): s({"xxx"}) {} };
struct U: S { U(): S({"xxx"}) {} };
S s({"xxx"});
The last is particularly interesting as it indicates that this may be related to bug 43453.