C++ Cast Operator Overloading – Watch Out for const
I spent far too much time on this problem today, but I've finally gotten it solved, and it's worthy of writing up. No question about it. The problem is in the variant class and the casting operators, or more properly conversion operators, that I put in place for the class.
To start out, it's a simple variant where we have a simple union as the ivar, and as the value type of the variant changes, the different components of the union are set. Pretty standard. What I wanted was to be able to use simple cast operators to get the values out of the variant:
// set the value to an int variant v = 10; // use the value count += (int) v;
and all was going pretty well when I had the operators for this variant class defined as:
operator varmap &() const; operator varmap *() const; operator varlist &() const; operator varlist *() const; operator int() const; operator int64_t() const; operator int64_t &(); operator float() const; operator double() const; operator double &(); operator std::string &() const; operator std::string *() const; operator uuid_t &() const; operator uuid_t *() const; operator bytes_t &() const; operator bytes_t *() const; operator error_t &() const; operator error_t *() const;
but when I added:
operator uint8_t() const; operator uint8_t &();
things really started to fall apart. Specifically, with the additional casting for (uint8_t), I got compiler warnings about:
// use the value value = (int) v;
saying the compiler could not figure out which one of the casting operators to use. There were several - both references and not. Very confusing. If I tried this:
// use the value value = (int64_t) v;
everything worked fine. Very odd. But I thought Hey, it's clearer to have the size in there anyway, let's just get rid of the problem operator. But that's never the real end of the problem, is it?
I next had the problem with:
// use the value value = (float) v;
Same thing. SO I really had to solve this. Bummer.
I spent about 90 mins trying all kinds of things, only to be blown away at the real solution: It's the const-ness of the casting operators. Change that, and it's all OK:
operator varmap &() const; operator varmap *() const; operator varlist &() const; operator varlist *() const; operator int(); operator int64_t() const; operator int64_t &(); operator uint8_t() const; operator uint8_t &(); operator float(); operator double() const; operator double &(); operator std::string &() const; operator std::string *() const; operator uuid_t &() const; operator uuid_t *() const; operator bytes_t &() const; operator bytes_t *() const; operator error_t &() const; operator error_t *() const;
Note lines 5 and 10 - no const. This allowed the compiler to figure out what it needed and I could put back in the (int) cast operator. What I'm guessing is that the (int) and (float) are really methods that create values as opposed to simply returning references or pointers to the members of the union. As such, making it const was not quite in line with what was happening. By allowing it to be returned as-is, the compiler was happier.
At least that's how I'm figuring it.