The line SpecialFloat f = 1.0f;
cannot perform assignment from 1.0f
to f
because f
doesn't exist yet. We are just creating it.
It would do if you had written SpecialFloat f{0.0f}; f = 1.0f
[Demo].
The line SpecialFloat f = 1.0f;
is doing copy initialization (1).
Initializes an object from another object.
Syntax
T object = other; (1)
In your code T
is SpecialFloat
, a class type, and other
is a float
(not T
or derived from T
).
The effects of copy initialization are:
...
If T is a class type, and the cv-unqualified version of the type of other is not T or derived from T [...] user-defined conversion sequences that can convert from the type of other to T are
examined and the best one is selected through overload resolution. The result of the conversion, which is a rvalue temporary [...] of the cv-unqualified version of T if a converting constructor was used, is then used to direct-initialize the object.
User-defined conversions from float
to SpecialFloat
should be examined. However, explicit constructors are not considered for copy-initialization.
Notes
Copy-initialization is less permissive than
direct-initialization: explicit constructors are not converting
constructors and are not considered for copy-initialization.
One way to solve this is to use direct initialization, and, if possible, with braces instead of parentheses, i.e. SpecialFloat f{1.0f};
[Demo].
There's a C++ Core Guideline advising about preferring the {}-initializer syntax.
Also, declaring single-argument constructors explicit is a general recommendation, so I would keep the user-declared constructor as explicit.
Another way would be to make SpecialFloat
class an aggregate, by removing the user-declared constructor, and use aggregate initialization, SpecialFloat f = {1.0f};
[Demo].
Finally, as commented by others, notice the signature of the assignment operator is SpecialFloat& operator=(const float f)
, what indicates that a SpecialFloat&
has to be returned. So first, update the object with m_float = f;
; then, return it with return *this;
.
[Edit]
I just came accross this article from Arthur O'Dwyer's, The Knightmare of Initialization in C++ where he basically favours copy initialization over direct initialization with braces, in order to improve the readability of the code.
Simple guidelines for variable initialization in C++:
- Use = whenever you can.
- Use initializer-list syntax {} only for element initializers (of containers and aggregates).
- Use function-call syntax () to call a constructor, viewed as an object-factory.
Thus:
int i = 0;
std::vector<int> v = {1, 2, 3, 4};
Widget w(name, price, quantity);
Moreover, he suggests to combine the copy initialization with the Almost Always Auto style. Going back to the original OP's question, that would allow us to keep the SpecialFloat
class untouched and write auto f = SpecialFloat{1.0f};
[Demo].
He acknowledges though that his guidelines conflict with the aforementioned C++ Core Guideline of preferring the {}-initializer syntax.