This happens while I was reviewing some cpp features.
U ({ arg1, arg2, ... }) (11)
cppreference #copy-list-initialization case 11
says
functional cast expression or other constructor invocations, where a brace-enclosed initializer list is used in place of a constructor argument. Copy-list-initialization initializes the constructor's parameter (note; the type U in this example is not the type that is being list-initialized;
U's constructor's parameter
is)
So I tried the following code to see which constructor will Cpp Compiler choose:
#include <string>
#include <vector>
#include <map>
#include <tuple>
#include <iostream>
using namespace std;
struct A {
static int id;
A(int integer, float sci, string str, vector<int> vnum, pair<int, int> intP, tuple<int, string, float> isfTuple) {
}
A(pair<int, float> ifP) { // this also allows {} for default construction
}
A(const A &) = delete;
A(A &&) = delete;
~A() {
cout << "Destructed: " << ++id << endl;
}
};
int main(){
A b({1, 1, "str", {1, 2}, {1, 1}, {1, "str", 1}}); // works on MinGW
// MinGW version: x86_64-14.2.0-release-win32-seh-ucrt-rt_v12-rev2
// MSVC error: 'A::A(A &&)': attempting to reference a deleted function
A partial({1, 1.0}); // works both on MinGW and MSVC with ISO C++20 Standard (/std:c++20)
}
For case A b(...)
, I was confused by why the compiler works though copy constructor A(const A &) = delete;
and move constructor A(A&&)
been deleted. Then I looked into Converting Construtor and copy elision and cpp draft on initializers only to get more confused.
Then I tried to compile with MSVC and got error:
'A::A(A &&)': attempting to reference a deleted function
Then Compiler Explorer succeeded.
So what is happening? MinGW or MSVC compiler bug? or this is Undefined Behavior (but case 11 seems to be well defined)? Why MinGW and Compiler Explorer succeeded?
My GCC build scripts (using vscode tasks.json):
g++ -Wall --std=gnu++20 -g ${file} -o ${fileDirname}\\${fileBasenameNoExtension}
Platform: Windows 11 of course.
Visual studio tool set: Visual Studio 2022 (v143)
Visual studio Language standard: ISO C++20 Standard (/std:c++20)
Edit:
My later tests show that both MSVC and MinGW use the same constructors for each case if no constructor deleted -- Result. The stdio result stays the same. There is only divergence in compiler checking behavoir (don't know what it is actually) and the compile result is the same , again, if no constructor deleted. And under -std=gnu++11 where copy elision is not available untill c++17, both MSVC and MinGW stays the same ouput -- no copy/move constructors were used.
So A b...
is actually use case 1 direct initialization syntax instead of case 11 which makes case 1's syntax conflict with the test case A b...
or is it actually using something else?
Edit:
It seems that copy elision is always happening since C++ 11 during initilization but some divergence happens in prvalue processing in assignment and return expression between compilers, I think the best practice is to avoid taking the minimal advantages of constructors' side effect.
Edit:
I think @Jarod42 is right.
{..}
has no type.
In A partial({1, 1.0});
,
we have to look for overload constructor of A
, the best viable constructor is A(pair<int, float>)
so {1, 1.0}
is used to construct the pair.
Similarly, for A b({1, 1, "str", {1, 2}, {1, 1}, {1, "str", 1}});
, the best viable constructor is (the deleted) A(A&&)
.
Selecting that constructor seems to make the program ill-formed for clang/msvc even if it would be elided with guaranty-copy elision (c++17).
Providing explicitly A
for {..}
(i.e A b(A{1, 1, "str", {1, 2}, {1, 1}, {1, "str", 1}});
) please all compilers Demo.