1

I'm currently struggling with the assignment operator. I keep missing something. Could you help me out here?

Check it out here https://godbolt.org/z/rfvTqcjoT

class SpecialFloat
{
    public:
    explicit SpecialFloat(const float f);

    SpecialFloat& operator=(const float f);
    private:
    float m_float;
};


SpecialFloat::SpecialFloat(const float f):
m_float(f)
{

}


SpecialFloat& SpecialFloat::operator=(const float f)
{
    m_float = f;
}

int main()
{
    SpecialFloat f = 1.0f;
}

why is my operator overloading not working?

<source>(27): error C2440: 'initializing': cannot convert from 'float' to 'SpecialFloat'
<source>(27): note: Constructor for class 'SpecialFloat' is declared 'explicit'

or can the assignment operator not take custom types?

5
  • 9
    SpecialFloat f = 1.0f; is not assignment, it's construction. Commented Jan 24, 2022 at 12:51
  • 3
    Your problem is in constructor, which is explicit. Constructor for class 'SpecialFloat' is declared 'explicit' Commented Jan 24, 2022 at 12:55
  • 2
    To support @Eljay: Taking away the explicit from your constructor (and fixing the assignment operator) makes your code compiling: Test of Compiler Explorer Commented Jan 24, 2022 at 12:56
  • ah alright thank you :) Commented Jan 24, 2022 at 13:04
  • 2
    Not related to the problem but you forgot to return *this; in your operator= Commented Jan 24, 2022 at 13:51

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

2 Comments

thx that was really helpfull. I'm usally a freid of making constructors explicit, but in this case it failed. So I learned, that SpecialFloat f = 1.0f; is not the same as SpecialFloat f{1.0f}; As proposed by you and the core guidelines I'll stick with the {} Initialiser :)
Glad to be helpful. I would recommend the use of the {} initializer almost everywhere. But beware of cases like std::vector<int> vi{5, 10}; where {} actually means std::initializer_list constructor Demo. Scott Meyers points out some cons of braced initialization in the chapter 7 of Effective Modern C++ (Distinguish between () and {} when creating objects.)
1

There are couple of issues as below.

SpecialFloat f = 1.0f;

Means you are trying to assign a float value to a SpecialFloat object. This works if constructor of SpecialFloat takes a float argument and if the constructor is not marked as explicit. But in your code, you marked the constructor as explicit. So object is not getting created and throwing error. If you want to know more about explicit constructor, read What does the explicit keyword mean?

Assignment operator overload function should return SpecialFloat object. You are not returning any thing which is wrong. It should return SpecialFloat object as below.

SpecialFloat& SpecialFloat::operator=(const float f)
{
    m_float = f;
    return *this;
}

Your understanding about assignment operator overloading function call is wrong. Assignment operator overloading function will be called when you are trying to assign an object to already created object.

SpecialFloat f = 1.0f;

Above statement is trying to create an object. So Assignment operator overloading function won't be called in this case.

1 Comment

thx this was a helpfull comment :) I missing this *this. However I'm usally a fan of making constructors explicit. I will try to apply @rturrado sollution. If this does not work for me, I will remove the explict keyword :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.