I'm working with a C++ class that includes both a conversion operator and a constructor that takes a std::initializer_list
.
According to Scott Meyers in "Effective Modern C++," Item 7, compilers strongly prefer to match braced initializers with constructors that accept std::initializer_list
s. This preference is so pronounced that it can override what would normally be copy and move construction, instead opting for the std::initializer_list
constructor.
However, in my tests, the conversion operator isn't being invoked as expected. I am guessing implicit copy operator is invoked instead. Below is a simplified version of the code from Scott Meyers' book:
#include <iostream>
#include <initializer_list>
#include <utility>
class Widget {
public:
Widget(int i, bool b) {
std::cout << "Widget(int, bool) called with values: " << i << ", " << b << std::endl;
}
Widget(int i, double d) {
std::cout << "Widget(int, double) called with values: " << i << ", " << d << std::endl;
}
Widget(std::initializer_list<long double> il) {
std::cout << "Widget(std::initializer_list<long double>) called with values: ";
for (auto value : il) {
std::cout << value << " ";
}
std::cout << std::endl;
}
operator float() const {
std::cout << "Conversion operator to float called" << std::endl;
// Dummy conversion logic; in a real class this would convert internal state to float
return 0.0f;
}
};
int main() {
Widget w1(10, true);
Widget w2(10, 5.0);
Widget w3{ 10, 5.0 };
Widget w4(10, 5.0);
Widget w5(w4);
Widget w6{ w4 }; // uses braces, calls
// std::initializer_list ctor
// (w4 converts to float, and float
// converts to long double)
Widget w7(std::move(w4));
Widget w8{std::move(w4) }; // uses braces, calls
// std::initializer_list ctor
// (for same reason as w6)
}
The comments for w6 and w8 are copied from the book.
output:
Widget(int, bool) called with values: 10, 1
Widget(int, double) called with values: 10, 5
Widget(std::initializer_list<long double>) called with values: 10 5
Widget(int, double) called with values: 10, 5
It's a bug in MSVC which uses the copy constructor in
Widget w6{w4};
and the move constructor in
Widget w8{std::move(w4)};
gcc, clang and icx all agree on the output you expect:
Widget(int, bool) called with values: 10, 1
Widget(int, double) called with values: 10, 5
Widget(std::initializer_list<long double>) called with values: 10 5
Widget(int, double) called with values: 10, 5
Conversion operator to float called
Widget(std::initializer_list<long double>) called with values: 0
Conversion operator to float called
Widget(std::initializer_list<long double>) called with values: 0
I also wrote a bugreport that you can vote for if you'd like this fixed.