Preamble
Before reading this, please be aware I am a C++ beginner. I have not learnt all the (basic) concepts yet (like templates for example), but I am attempting the understand some fundamentals fully before moving on to something else.
So please do not mention std::string or std:array, vectors, boost::arrays, and their superiority to C-style arrays. I am sure their are, but that is not the point here :)
I also now that it is sometimes preferred to assign elements in the ctor body than using its member initializer list.
My Question
Considering the following example. When I reached the moment I had to define a copy constructor to initialize my character2 declared in main(), I noticed, in Visual Studio 2017, that IntelliSense displays 3 different overloads to initialize the class members from the initializer list. For example, with the 'name' variable, the available listed overloads are:
char [50](const char [50] &)
char [50](char [50] &&)
char [50]()
Subsidiary question 1: Why IntelliSense allows to display these overloads only when the variable is declared as class member ? (nothing is shown with CTRL+SHIFT+SPACE if declared in main()
)
The one that intrigues me most is the one with an rvalue reference in it :
char [50](char [50] &&)
for the 'name' variableint [10](int [10] &&)
for the 'data' variableint(int &&)
for the 'singledata' variable
Now, if I use name()
in an initializer list, overload #3 appears to be used. If I use name("aaa")
, overload #1 appears to be used. This got me to investigate on other concepts: I now understand the concepts of lvalue, rvalue, and lvalue references (but not this, too much complicated for me right now), and I fail to completely understand the concept of rvalue reference and move semantics.
Considering my partial understanding, I tried to declare an rvalue reference in my "parameterized constructor 2" (which then becomes an lvalue of type "rvalue reference to int") and to initialize the 'singledata' variable with it, hoping to see the initializer overload #3 poping up in IntelliSense, but it does not. The one with the lvalue reference parameter (overload #2) appears to be used again in this case. That I can't explain.
So here I am, stuck, and this is my MAIN QUESTION 1: When does the initializer with an rvalue reference parameter happen to be used for a class member?
MAIN QUESTION 2: Why is it not possible to initialize a char array using another char array while it is possible using string literal expressions? because there are pure rvalues (prvalues) while a variable could only be converted to an xvalue expression at best?
I know this is absolutely not standard, but if I look at the dissassembly, the initialization of an array of chars is quite simple:
2: const char arrchar1[10]("hello");
01142771 A1 38 9B 14 01 mov eax,dword ptr [string "hello" (01149B38h)]
01142776 89 45 D8 mov dword ptr [arrchar1],eax
01142779 66 8B 0D 3C 9B 14 01 mov cx,word ptr ds:[1149B3Ch]
01142780 66 89 4D DC mov word ptr [ebp-24h],cx
01142784 33 C0 xor eax,eax
01142786 89 45 DE mov dword ptr [ebp-22h],eax
the stack-allocated space [arrchar1]
(being [ebp-28h]
in my case) in the current stack frame is simply filled by a MOV
instruction set (2, really, in this case: 1 dword is moved for "hell", then 1 word is moved for the rest) with the contents of the static space containing the "hello" string literal.
Why is there no C++ way resulting in something similar once compiled, which would perform the same thing but 'moving' the contents of a stack-allocated space (another array variable), rather than 'moving' some static memory? I would expect something like this: char arrchar2[10](arrchar1)
Example:
#include <string.h> // for strcpy
class Character
{
public:
//default constructor
Character() {};
//parameterized constructor 1
Character(const char * pname) : // pointer to the string literal must be const (otherwise it is Undefined Behaviour)
name(), data{ 1, 2, 3 } // mem-initializer-list:
// - name is initialized with value-initialization (empty expression-list in a pair of parentheses following identifier)
// - (C++11) data is initialized using list-initialization which becomes aggregate-initialization since it is an aggregate (array)
{
strcpy_s(name, pname);
};
//parameterized constructor 2
Character(const char * pname, int &&val1) :
name(), data{ 1, 2, 3 }, singledata(val1)
{
strcpy_s(name, pname);
};
//copy constructor
Character(const Character & tocopy)
// member initializer list
//:name(), // >> IntelliSense shows 3 initializer overloads: char [50](const char [50] &) || char [50](char [50] &&) || char [50]()
// >> (visible only if the array is declared in a class, no pop up in main...)
: name("aaa"),
//data() // >> IntelliSense shows 3 initializer overloads: int [10](const int [10] &) || int [10](int [10] &&) || int [10]()
// >> (visible only if the array is declared in a class, no pop up in main...)
data{ 1, 2, 3 }
{
// ctor body definition
};
private:
char name[50];
int data[10];
int singledata;
};
void main()
{
Character character1("characterOne"); // the string literal has static storage duration (static memory), passed pointer allocated on stack memory
Character character2(character1);
Character character3("characterThree", 3);
}