2

I have code to overload the conversion operator for different types:

#include <string>
#include <iostream>

class MyClass {
   int m_int;
   double m_double;
   std::string m_string;

public:
   MyClass() : m_int{1}, m_double(1.2), m_string{"Test"} {};

   // overload the conversion operator for std::string
   operator std::string() {
      return m_string;
   }

   // overload the conversion operator for all other types
   template <typename T>
   operator T() {
      if (std::is_same<T, int>::value)
         return m_int;
      else
         return m_double;
   }
};

int main() {

   MyClass o;

   int i = o;
   double d = o;
   std::string s = o;

   std::cout << i << " " << " " << d << " " << s << std::endl;
}

This works correctly. But when I try do do

std::string s;
s = o;

the compilation fails with

error: use of overloaded operator '=' is ambiguous (with operand types 'std::string' (aka 'basic_string<char, char_traits<char>, allocator<char> >') and 'MyClass')

Apparently the compiler instantiates a conversion operator different than std::string(). If I remove the template section, the compiler is forced to use the std::string() conversion operator and it works again. So what am I missing?

JaMiT
  • 14,422
  • 4
  • 15
  • 31
Stefan
  • 51
  • 3

2 Answers2

3

In std::string class, it is possible to use operator=() to assign characters (and as a result integers) to the std::string. It means that the following code is acceptable because the operator=(char) does exist:

std::string s1;

s1 = '1'; // works fine
s1 = 24; // works fine

But you cannot copy construct an string using characters (and so integers).

std::string s2  = '1'; // compilation fails
std::string s3 = 24;   // compilation fails

So, in your case, when you use copy-construction, there is no ambiguity, because std::string cannot be copy constructed using double or int. But for operator= it is ambiguous because you can assign integers into a string and the compiler does not know which conversion operator to use. In other words, the compiler can use both your string and int conversion operators for that.

TonySalimi
  • 8,257
  • 4
  • 33
  • 62
  • 1
    Might wanna mention the assignment operator is overloaded only for `char` (in the case of `std::basic_string`) so `int` and `double` get implcitly converted – Object object Apr 14 '20 at 10:32
  • I understand. But how can I convince the compiler to assign from string and not do any conversion? – Stefan Apr 14 '20 at 11:19
  • @Stefan I highly recommend you NOT to overuse implicit conversion operators in your code. Check this answer: https://stackoverflow.com/a/22164767/896012. BTW, why not putting 3 different getters in your class? I mean, you should be explicit about which data member you wanna return, no? – TonySalimi Apr 14 '20 at 12:16
  • @Stefan Definatly do not overuse implcit conversion operators and here is kinda a textbook reason why, but if you _must_ have a conversion operator make it `explicit` and use `static_cast()` on it. Honestly though its far better to just have a `to_string()` method which returns the `std::string` – Object object Apr 14 '20 at 12:26
  • @LonesomeParadise Can you re-write the original Q's code somewhere using this `explicit` and `static_cast` mechanism? I am interested to now how it can solve the problem. – TonySalimi Apr 14 '20 at 12:48
  • @Gupta Thanks for your comments. I understand that I can use different getters, but my goal is to make the syntax `s = o` working. I DO NOT want explicit conversion like `s = static_casto;`. So do you see any way to rewrite the class to make the syntax `s = o` working. My original program is very simplified, but I DO NEED different types being stored in one class for some good reason. – Stefan Apr 14 '20 at 15:08
  • @Gupta Can if you want but I may have not been clear :p I simply meant make the non-template conversion operator `explicit` and then use a (now required) cast. The use of `explicit` is of course not _technically_ needed here but shows that you _have_ to cast it (which is the point). My other point was simply to use a `to_string()` method instead of the conversion operator – Object object Apr 14 '20 at 17:08
0

Got it kind of working (thanks to my colleague NB):

#include <string>
#include <iostream>

class MyClass {
   int m_int;
   double m_double;
   std::string m_string;

public:
   MyClass() : m_int{1}, m_double(1.2), m_string{"Test"} {};

   // overload the conversion operator for std::string
   operator std::string() {
      return m_string;
   }

   // overload the conversion operator for all other types
   template <typename T, typename std::enable_if<
           std::is_same<T, int>::value ||
           std::is_same<T, double>::value
           ,T>::type* = nullptr>
   operator T() {
      if (std::is_same<T, int>::value)
         return m_int;
      else
         return m_double;
   }
};

int main() {

   MyClass o;

   int i = o;
   double d = o;
   std::string s;
   s = o;

   std::cout << i << " " << " " << d << " " << s << std::endl;
}

So one has to enable the template for all valid types. But as soon as (char) is in the type list under std::enable_if<>, the error occurs again. Apparently the std::string= operator wants to map to char (See item 4. above).

What I do not understand right now is why explicitly specifying the int() and double() conversion operators explicitly does NOT work:

operator int() {
   return m_int;
}

operator double() {
   return m_double;
}

although this should be equivalent to

   template <typename T, typename std::enable_if<
           std::is_same<T, int>::value ||
           std::is_same<T, double>::value
           ,T>::type* = nullptr>
   operator T() {
      if (std::is_same<T, int>::value)
         return m_int;
      else
         return m_double;
   }

Also not clear why this does not work:

template <typename T, typename std::enable_if<
        std::is_same<T, int>::value ||
        std::is_same<T, double>::value ||
        std::is_same<T, std::string>::value
        ,T>::type* = nullptr>
operator T() {
   if (std::is_same<T, std::string>::value)
      return m_string;
   else if (std::is_same<T, int>::value)
      return m_int;
   else
      return m_double;
}

Maybe someone can give me some insight.

Stefan
  • 51
  • 3