EDIT: I don't think this a duplicate of this other question, because the other question simply transposes () for {} in the constructors. Whereas I note different behavior when a constructor is defined in a struct, but not in a class. (And, as pointed out in the comments, this is about using constructors not writing them.) But I've been wrong before.
I came across this strange (to me) syntax for a constructor while tutoring:
Foo obj {i, j};
At first I thought it wouldn't work, and told the student to rewrite it – however they were adamant it worked, and informed me they pulled the example from cplusplus.com, to which I've not been able to find a reference, so I tried it anyway... And it worked. So I experimented with it.
I also researched a bit on my own, and found no reference to that kind of constructor syntax on cplusplus.com. (Maybe it has a specific name?)
Here's what I did to experiment with it.
struct Note { //A musical note.
std::string name;
double freq;
//Note(std::string s, double f): name(s), freq(f){}
//Uncomment the constructor in order to use normal constructor syntax.
};
class Journal {
public:
std::string title;
std::string message;
int idNum;
};
int main() {
Note a { "A", 440.0}; //Works with or without a constructor.
//Note a("A",440.0); //Works ***only*** with a defined constructor.
//Journal journal("hello, world", "just me, a class", 002); //Works regardless of constructor definition.
Journal journal {"hello, world", "just me, a class", 003}; //Works regardless of constructor definition.
std::cout << a.name << " " << a.freq << std::endl;
std::cout << journal.title << " " << journal.message << " " << journal.idNum << std::endl;
return 0;
}
I found that it works with structs and classes regardless if they have a defined constructor.
Obviously default constructors are at work, but this confused me because of a few reasons:
I've never seen this syntax before (not surprising, C++ is huge)
It actually works
Works regardless of a constructor being defined, so may be default behavior
My question is:
Is there a name and specific purpose for this syntax which sets it apart from regular constructor behavior, and if so, why use it (or not)?
Things have changed since the introduction of C++11 and you should probably read about list initialization
int x (0); // Constructor initialization
int x {0}; // Uniform initialization
Before its introduction you had various initialization cases:
objects by calling the usual () constructor (and watch out for most vexing parse if no arguments are present)
aggregate classes or arrays with {}
default constructing with no braces
Now you can use uniform initialization in all of them.
It has to be noted that the two aren't really interchangeable since
std::vector<int> v (100); // 100 elements
std::vector<int> v {100}; // one element of value 100
and you have to pay attention to the fact that if the type has an initializer list constructor, it will take precedence in overload resolution.
That said, uniform initialization can be quite handy and safe (preventing narrowing conversions).
That syntax is called "list initialization". You can read more about it in Section 8.5.4 of C++14.
I would guess that this syntax mainly exists to be backwards compatible with C, which had syntax for initializing structs and arrays that looked very similar.
Related
In C++11, we can write this code:
struct Cat {
Cat(){}
};
const Cat cat;
std::move(cat); //this is valid in C++11
when I call std::move, it means I want to move the object, i.e. I will change the object. To move a const object is unreasonable, so why does std::move not restrict this behaviour? It will be a trap in the future, right?
Here trap means as Brandon mentioned in the comment:
" I think he means it "traps" him sneaky sneaky because if he doesn't
realize, he ends up with a copy which is not what he intended."
In the book 'Effective Modern C++' by Scott Meyers, he gives an example:
class Annotation {
public:
explicit Annotation(const std::string text)
: value(std::move(text)) //here we want to call string(string&&),
//but because text is const,
//the return type of std::move(text) is const std::string&&
//so we actually called string(const string&)
//it is a bug which is very hard to find out
private:
std::string value;
};
If std::move was forbidden from operating on a const object, we could easily find out the bug, right?
There's a trick here you're overlooking, namely that std::move(cat) doesn't actually move anything. It merely tells the compiler to try to move. However, since your class has no constructor that accepts a const CAT&&, it will instead use the implicit const CAT& copy constructor, and safely copy. No danger, no trap. If the copy constructor is disabled for any reason, you'll get a compiler error.
struct CAT
{
CAT(){}
CAT(const CAT&) {std::cout << "COPY";}
CAT(CAT&&) {std::cout << "MOVE";}
};
int main() {
const CAT cat;
CAT cat2 = std::move(cat);
}
prints COPY, not MOVE.
http://coliru.stacked-crooked.com/a/0dff72133dbf9d1f
Note that the bug in the code you mention is a performance issue, not a stability issue, so such a bug won't cause a crash, ever. It will just use a slower copy. Additionally, such a bug also occurs for non-const objects that don't have move constructors, so merely adding a const overload won't catch all of them. We could check for the ability to move construct or move assign from the parameter type, but that would interfere with generic template code that is supposed to fall back on the copy constructor.
And heck, maybe someone wants to be able to construct from const CAT&&, who am I to say he can't?
struct strange {
mutable size_t count = 0;
strange( strange const&& o ):count(o.count) { o.count = 0; }
};
const strange s;
strange s2 = std::move(s);
here we see a use of std::move on a T const. It returns a T const&&. We have a move constructor for strange that takes exactly this type.
And it is called.
Now, it is true that this strange type is more rare than the bugs your proposal would fix.
But, on the other hand, the existing std::move works better in generic code, where you don't know if the type you are working with is a T or a T const.
One reason the rest of the answers have overlooked so far is the ability for generic code to be resilient in the face of move. For example lets say that I wanted to write a generic function which moved all of the elements out of one kind of container to create another kind of container with the same values:
template <class C1, class C2>
C1
move_each(C2&& c2)
{
return C1(std::make_move_iterator(c2.begin()),
std::make_move_iterator(c2.end()));
}
Cool, now I can relatively efficiently create a vector<string> from a deque<string> and each individual string will be moved in the process.
But what if I want to move from a map?
int
main()
{
std::map<int, std::string> m{{1, "one"}, {2, "two"}, {3, "three"}};
auto v = move_each<std::vector<std::pair<int, std::string>>>(m);
for (auto const& p : v)
std::cout << "{" << p.first << ", " << p.second << "} ";
std::cout << '\n';
}
If std::move insisted on a non-const argument, the above instantiation of move_each would not compile because it is trying to move a const int (the key_type of the map). But this code doesn't care if it can't move the key_type. It wants to move the mapped_type (std::string) for performance reasons.
It is for this example, and countless other examples like it in generic coding that std::move is a request to move, not a demand to move.
I have the same concern as the OP.
std::move does not move an object, neither guarantees the object is movable. Then why is it called move?
I think being not movable can be one of following two scenarios:
1. The moving type is const.
The reason we have const keyword in the language is that we want the compiler to prevent any change to an object defined to be const. Given the example in Scott Meyers' book:
class Annotation {
public:
explicit Annotation(const std::string text)
: value(std::move(text)) // "move" text into value; this code
{ … } // doesn't do what it seems to!
…
private:
std::string value;
};
What does it literally mean? Move a const string to the value member - at least, that's my understanding before I reading the explanation.
If the language intends to not do move or not guarantee move is applicable when std::move() is called, then it is literally misleading when using word move.
If the language is encouraging people using std::move to have better efficiency, it has to prevent traps like this as early as possible, especially for this type of obvious literal contradiction.
I agree that people should be aware moving a constant is impossible, but this obligation should not imply the compiler can be silent when obvious contradiction happens.
2. The object has no move constructor
Personally, I think this is a separate story from OP's concern, as Chris Drew said
#hvd That seems like a bit of a non-argument to me. Just because OP's suggestion doesn't fix all bugs in the world doesn't necessarily mean it is a bad idea (it probably is, but not for the reason you give). – Chris Drew
I'm surprised nobody mentioned the backward compatibility aspect of this. I believe, std::move was purposely designed to do this in C++11. Imagine you're working with a legacy codebase, that heavily relies on C++98 libraries, so without the fallback on copy assignment, moving would break things.
Fortunately you can use clang-tidy's check to find such issues:
https://clang.llvm.org/extra/clang-tidy/checks/performance/move-const-arg.html
I have read (Bjarne Stroustrup, The C++ Programming Language, 6.3.5) about using initializer_list when initializing a variable, so that you don't have a narrowing conversion. Bjarne recommends only using direct-list-initialization :
X a1 {v};
X a2 = {v};
X a3 = v;
X a4(v);
Of these, only the first can be used in every context, and I strongly
recommend its use. It is clearer and less error-prone than the
alternatives.
Why does Bjarne only recommend the first one?
Why isn't it recommended to do initializer_list in assignment (rather than initialization)? Or is it just implied that you should do that?
a1 = {v};
Here is an example of what I am asking about? Why is initializer_list not recommended for assignment (from what I can tell) but it is recommended for initialization? It seems like it is beneficial by reducing potential narrowing conversions on accident.
char c;
int toobig = 256;
c = 256; //no error, narrowing occurs
c = { toobig }; //narrowing conversion, error
Initializer lists are usually recommended and overcome a syntactic trap called 'The Most Vexxing Parse'.
std::vector<int> v();
Inside a function this looks like variable declaration but it's a declaration of a function v taking no arguments returning a std::vector<int>.
OK so std::vector<int> v; fixes that one but in a template a parameter may be a built in type where int x; leaves x uninitialized but int x{}; (value) initializes it to zero.
In modern C++ with copy elision and a couple of syntatic rules, there aren't that many ways to accidentally create temporary copies in a variable declaration.
But initializer-lists do clear up a couple of anomalies and are to be recommended.
Overloading in C++ is quite keen and there remain reasons to sometimes use () to call the appropriate constructor.
For example std::vector<T> can take a initializer-list of values and create an array containing that list. Great.
It can also take count and value arguments and create an array of count copies of `value'. Also great.
But if the size type is compatible with the value-type (T) you can still get a surprise!
#include <iostream>
#include <vector>
template<typename T>
void dump_vector(const std::string& tag,const std::vector<T>& vec);
int main() {
std::vector<int> v1(5,20);
std::vector<int> v2{5,20};
std::vector<std::string> v3(5,"Hi!");
std::vector<std::string> v4{5,"Hi!"};
dump_vector("v1",v1);
dump_vector("v2",v2);
dump_vector("v3",v3);
dump_vector("v4",v4);
return 0;
}
template<typename T>
void dump_vector(const std::string& tag,const std::vector<T>& vec){
std::cout<< tag << "={ ";
auto begin=vec.begin();
auto end=vec.end();
for(auto it{begin};it!=end;++it){
if(it!=begin){
std::cout<<", ";
}
std::cout<<*it;
}
std::cout << " }\n";
}
Expected output:
v1={ 20, 20, 20, 20, 20 }
v2={ 5, 20 }
v3={ Hi!, Hi!, Hi!, Hi!, Hi! }
v4={ Hi!, Hi!, Hi!, Hi!, Hi! }
The versions with () and {} did different things for std::vector<int> but landed on the same constructor for std::vector<std::string>.
This isn't a new or unique problem. C++ uses types heavily in selecting an overload and when there's a bunch of candidates different template instantiations might make an unexpected choice!
I'd say initializer-list is now preferred. But when a constructor that itself takes in initializer list exists, you may need to be explict if you don't want to hit it.
Also worth a read http://read:%20https://herbsutter.com/2013/05/09/gotw-1-solution/
Take this as example
#include <iostream>
struct foo
{
explicit foo(int)
{
std::cout << "[+] c'tor called\n";
}
foo(const foo&)
{
std::cout << "[+] copy c'tor called\n";
}
};
int main()
{
std::cout << "\ncreating object a\n";
foo a = foo{1};
std::cout << "\n\ncreating object b\n";
foo b{1};
}
Compiling with g++ main.cpp --std=c++11 -fno-elide-constructors
Output:
creating object a
[+] c'tor called
[+] copy c'tor called
creating object b
[+] c'tor called
In the first a temporary is created and then a is created by calling the copy constructor.
Whereas in second example the object is directly created.
There are few cases where you have to use () syntax instead of {}.
Refer to Scott Meyers-Effective Modern C++ (item 7)`
This sample program shows how a different constructor will be called depending on whether you pass in a local variable, a global variable, or an anonymous variable. What is going on here?
std::string globalStr;
class aClass{
public:
aClass(std::string s){
std::cout << "1-arg constructor" << std::endl;
}
aClass(){
std::cout << "default constructor" << std::endl;
}
void puke(){
std::cout << "puke" << std::endl;
}
};
int main(int argc, char ** argv){
std::string localStr;
//aClass(localStr); //this line does not compile
aClass(globalStr); //prints "default constructor"
aClass(""); //prints "1-arg constructor"
aClass(std::string("")); //also prints "1-arg constructor"
globalStr.puke(); //compiles, even though std::string cant puke.
}
Given that I can call globalStr.puke(), I'm guessing that by calling aClass(globalStr);, it is creating a local variable named globalStr of type aClass that is being used instead of the global globalStr. Calling aClass(localStr); tries to do the same thing, but fails to compile because localStr is already declared as a std::string. Is it possible to create an anonymous instance of a class by calling its 1-arg constructor with a non-constant expression? Who decided that type(variableName); should be an acceptable way to define a variable named variableName?
aClass(localStr); //this line does not compile
This tries to declare a variable of type aClass named localStr. The syntax is terrible, I agree, but it's way too late for that [changing the standard] now.
aClass(globalStr); //prints "default constructor"
This declares one called globalStr. This globalStr variable hides the global one.
aClass(""); //prints "1-arg constructor"
This creates a temporary object of type aClass.
aClass(std::string("")); //also prints "1-arg constructor"
This also creates a temporary.
globalStr.puke(); //compiles, even though std::string cant puke.
This uses the globalStr in main, which is consistent with every other instance of shadowing.
Is it possible to create an anonymous instance of a class by calling its 1-arg constructor with a non-constant expression?
Yes, I can think of four ways:
aClass{localStr}; // C++11 list-initialization, often called "uniform initialization"
(void)aClass(localStr); // The regular "discard this result" syntax from C.
void(aClass(localStr)); // Another way of writing the second line with C++.
(aClass(localStr)); // The parentheses prevent this from being a valid declaration.
As a side note, this syntax can often be the cause of the Most Vexing Parse. For example, the following declares a function foo that returns aClass, with one parameter localStr of type std::string:
aClass foo(std::string(localStr));
Indeed it's the same rule that's responsible for your problems - If something can be parsed as a valid declaration, it must be. That is why aClass(localStr); is a declaration and not a statement consisting of a lone expression.
In C++11, we can write this code:
struct Cat {
Cat(){}
};
const Cat cat;
std::move(cat); //this is valid in C++11
when I call std::move, it means I want to move the object, i.e. I will change the object. To move a const object is unreasonable, so why does std::move not restrict this behaviour? It will be a trap in the future, right?
Here trap means as Brandon mentioned in the comment:
" I think he means it "traps" him sneaky sneaky because if he doesn't
realize, he ends up with a copy which is not what he intended."
In the book 'Effective Modern C++' by Scott Meyers, he gives an example:
class Annotation {
public:
explicit Annotation(const std::string text)
: value(std::move(text)) //here we want to call string(string&&),
//but because text is const,
//the return type of std::move(text) is const std::string&&
//so we actually called string(const string&)
//it is a bug which is very hard to find out
private:
std::string value;
};
If std::move was forbidden from operating on a const object, we could easily find out the bug, right?
There's a trick here you're overlooking, namely that std::move(cat) doesn't actually move anything. It merely tells the compiler to try to move. However, since your class has no constructor that accepts a const CAT&&, it will instead use the implicit const CAT& copy constructor, and safely copy. No danger, no trap. If the copy constructor is disabled for any reason, you'll get a compiler error.
struct CAT
{
CAT(){}
CAT(const CAT&) {std::cout << "COPY";}
CAT(CAT&&) {std::cout << "MOVE";}
};
int main() {
const CAT cat;
CAT cat2 = std::move(cat);
}
prints COPY, not MOVE.
http://coliru.stacked-crooked.com/a/0dff72133dbf9d1f
Note that the bug in the code you mention is a performance issue, not a stability issue, so such a bug won't cause a crash, ever. It will just use a slower copy. Additionally, such a bug also occurs for non-const objects that don't have move constructors, so merely adding a const overload won't catch all of them. We could check for the ability to move construct or move assign from the parameter type, but that would interfere with generic template code that is supposed to fall back on the copy constructor.
And heck, maybe someone wants to be able to construct from const CAT&&, who am I to say he can't?
struct strange {
mutable size_t count = 0;
strange( strange const&& o ):count(o.count) { o.count = 0; }
};
const strange s;
strange s2 = std::move(s);
here we see a use of std::move on a T const. It returns a T const&&. We have a move constructor for strange that takes exactly this type.
And it is called.
Now, it is true that this strange type is more rare than the bugs your proposal would fix.
But, on the other hand, the existing std::move works better in generic code, where you don't know if the type you are working with is a T or a T const.
One reason the rest of the answers have overlooked so far is the ability for generic code to be resilient in the face of move. For example lets say that I wanted to write a generic function which moved all of the elements out of one kind of container to create another kind of container with the same values:
template <class C1, class C2>
C1
move_each(C2&& c2)
{
return C1(std::make_move_iterator(c2.begin()),
std::make_move_iterator(c2.end()));
}
Cool, now I can relatively efficiently create a vector<string> from a deque<string> and each individual string will be moved in the process.
But what if I want to move from a map?
int
main()
{
std::map<int, std::string> m{{1, "one"}, {2, "two"}, {3, "three"}};
auto v = move_each<std::vector<std::pair<int, std::string>>>(m);
for (auto const& p : v)
std::cout << "{" << p.first << ", " << p.second << "} ";
std::cout << '\n';
}
If std::move insisted on a non-const argument, the above instantiation of move_each would not compile because it is trying to move a const int (the key_type of the map). But this code doesn't care if it can't move the key_type. It wants to move the mapped_type (std::string) for performance reasons.
It is for this example, and countless other examples like it in generic coding that std::move is a request to move, not a demand to move.
I have the same concern as the OP.
std::move does not move an object, neither guarantees the object is movable. Then why is it called move?
I think being not movable can be one of following two scenarios:
1. The moving type is const.
The reason we have const keyword in the language is that we want the compiler to prevent any change to an object defined to be const. Given the example in Scott Meyers' book:
class Annotation {
public:
explicit Annotation(const std::string text)
: value(std::move(text)) // "move" text into value; this code
{ … } // doesn't do what it seems to!
…
private:
std::string value;
};
What does it literally mean? Move a const string to the value member - at least, that's my understanding before I reading the explanation.
If the language intends to not do move or not guarantee move is applicable when std::move() is called, then it is literally misleading when using word move.
If the language is encouraging people using std::move to have better efficiency, it has to prevent traps like this as early as possible, especially for this type of obvious literal contradiction.
I agree that people should be aware moving a constant is impossible, but this obligation should not imply the compiler can be silent when obvious contradiction happens.
2. The object has no move constructor
Personally, I think this is a separate story from OP's concern, as Chris Drew said
#hvd That seems like a bit of a non-argument to me. Just because OP's suggestion doesn't fix all bugs in the world doesn't necessarily mean it is a bad idea (it probably is, but not for the reason you give). – Chris Drew
I'm surprised nobody mentioned the backward compatibility aspect of this. I believe, std::move was purposely designed to do this in C++11. Imagine you're working with a legacy codebase, that heavily relies on C++98 libraries, so without the fallback on copy assignment, moving would break things.
Fortunately you can use clang-tidy's check to find such issues:
https://clang.llvm.org/extra/clang-tidy/checks/performance/move-const-arg.html
Consider this code:
class First
{
public:
int y;
First()
{
y = 90;
}
};
class Second{
public:
int x;
First ob; //if i comment it, value of x is initialised to zero
};
int main()
{
Second obj = Second();
cout << obj.x << endl;
}
This program gives different output when I change it:
If I comment out the line First ob; then value of x is initialised to zero
If the class consists object of class First, then member has garbage value.
If I create object like Second obj; then it has garbage value.
What is the reason it behaves differently when Second class consists only built-in members and when object of another class?
And what is the difference between these statements:
Second obj = Second();
Second obj;
Case 1. When you comment out the line First ob, then Second class becomes a POD type, which can be value-initialized using the syntax Second obj= Second(), and value-initialization, in case of built-in types, means zero-initialized, so x is zero-initialized.
Case 2: When you keep the line First ob, then Second class becomes non-POD type, because First class is non-POD type (as it has user-defined constructor1]). In this case, the syntax Second obj=Second() doesn't initialize built-in data type, if you don't initialize it in the constructor.
Case 3: When write Second obj, then there are again two cases (read them carefully):
obj will be default constructed if there is First ob line. And in this case, x will not be initialized at all, as it is not manually initialized in the constructor of Second.
obj will not be initialized at all if you First ob line is commented out. In this case, Second becomes a POD, and POD types are not initialized if you only write Second obj.
1 See this related topic:
Can't C++ POD type have any constructor?
Have a read through the accepted answer to Do the parentheses after the type name make a difference with new? - without the First object, your class is an 'A', with it your class is a 'B'. Second obj; will default-initialize it, Second obj = Second(); will value-initialize it.
This is one of the areas where C++ has some quite tricky and surprising rules, but I'm not going to cover them in detail here as they've been very well explained in the above question.
What do the following phrases mean in C++: zero-, default- and value-initialization? is also related.
Edit:
The above links only really cover the initialization side of things, not the rules on what makes a class trivial/POD/standard layout/etc. What are Aggregates and PODs and how/why are they special? covers that side of things.
At the risk of being a bit tangential, let me post a modified version of the problem, along with a discussion. Let's consider these classes:
struct Agatha
{
int x;
};
struct Claire
{
Claire() { }
int x;
};
To simplify the initialization syntax and make the example analyzable with Valgrind, let's use dynamic objects:
#include <memory>
#include <iostream>
int main()
{
std::unique_ptr<Agatha> p1(new Agatha); // #1d
std::unique_ptr<Agatha> p2(new Agatha()); // #1v
std::unique_ptr<Claire> q1(new Claire); // #2d
std::unique_ptr<Claire> q2(new Claire()); // #2v
std::cout << p1->x
<< p2->x
<< q1->x
<< q2->x
<< std::endl;
}
Only one of those printing lines is correct! Can you figure out which one?
The answer is #1v. Let's discuss:
Case #1d default-initializes an aggregate, which default-initializes each member, which default-initializes Agatha::x, which leaves it uninitialized.
Case #1v value-initializes a aggregate, which value-initializes all members, and thus value-initializes Agatha::x, and thus zero-initializes it. (This is the good case.)
Case #2d default-initializes a non-aggregate, which calls the default constructor. The default constructor of Claire does not initialize Claire::x, so it is left uninitialized.
Case #2d value-initialization of a non-aggregate also calls the default constructor, and the situation is identical to #2v.
Notice that the whole discussion has nothing to do with PODness, but merely with whether the class is an aggregate, like Agatha, or a non-trivial class type with like Claire. You could add a member of type std::string to Agatha without affecting this example.