3

I wonder if there is a possibility in c++ to achieve the same cast overloading like in this C# example:

class A {
    public static implicit operator A(string s) {
        return new A();
    }
    public static implicit operator A(double d) {
        return new A();
    }        
    static void Main(string[] args) {
        A a = "hello";
        A b = 5.0;
    }
}

In C++ it should be something like this:

#include <string>
using namespace std;

class A{
    /*SOME CAST OVERLOADING GOES HERE*/
};


void main(){
    A a = "hello";
    A b = 5.0;
}

Can you help me how I can make this cast overloading?

Van Coding
  • 24,244
  • 24
  • 88
  • 132

5 Answers5

17

This is typically achieved with constructors:

class A
{
public:
  A(const std::string & s) { /*...*/ }
  A(double d)              { /*...*/ }
  //...
};

Usage: A a("hello"), b(4.2), c = 3.5, d = std::string("world");

(If you declare the constructor explicit, then only the first form ("direct initialization", with the parentheses) is allowed. Otherwise the two forms are entirely identical.)

A one-parameter constructor is also called a "conversion constructor" for this very reason: You can use it to construct an object from some other object, i.e. "convert" a double, say, to an A.

Implicit conversion is widely used. For example, it'll apply in the following situation:

void f(A, const A&) { /* ... */ }

int main() { f(1.5, std::string("hello")); }

This constructs temporaries A(1.5) and A("hello") and passes them to f. (Temporaries also bind to constant references, as you can see with the second argument.)

Update: (Credits to @cHao for looking this up.) According to the standard (12.3.4), "At most one user-defined conversion (constructor or conversion function) is implicitly applied to a single value." (This refers to implicit conversion; direct-initialization a la A a("hello"); doesn't fall under this*. See this related question.) So unfortunately you cannot say f("hello", "world");. (Of course you can add a const char * constructor, which in the new C++11 you can even forward to the string constructor with almost no effort.)

*) I think this is actually a bit more subtle. Only user-defined conversions are affected by the rule. So first off, you get a fundamental conversion from char (&)[6] to const char * for free. Then you get one implicit conversion from const char * to std::string. Finally, you have an explicit conversion-construction from std::string to A.

Community
  • 1
  • 1
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • I'd rather say, "In C++, you'd use constructors for that". In the C# code he has, they are implicit cast operators, and would not be called constructors. – Ben Voigt Nov 15 '11 at 15:35
  • 3
    Note also that C++ constructors are _implicit_ by default - that is they can be used by the compiler for automatic typecasts. Add the _explicit_ keyword before a single-argument constructor if you want to prohibit this. – Useless Nov 15 '11 at 15:37
  • 1
    Holy...! That´s way the better solution than in C# :S I never noticed that I can instanciate an object like this =) – Van Coding Nov 15 '11 at 15:37
  • 1
    @KerrekSB: I don't think the `d="blah"` form works: ` note: candidate constructor not viable: no known conversion from 'const char [6]' to 'const std::string &'` accoding to clang++ – Mat Nov 15 '11 at 15:38
  • @VanCoding: I shall resist the temptation to comment on that statement, but do feel free to bring it up again in the chat room :-) – Kerrek SB Nov 15 '11 at 15:39
  • Might also be good to show implicit conversion in a context other than declaration of a variable, e.g. as the actual parameter of a function call. – Ben Voigt Nov 15 '11 at 15:40
  • @Mat: hah, indeed. Strange. I edited it to make the string explicit. – Kerrek SB Nov 15 '11 at 15:42
  • @KerrekSB: or add a `const char*` constructor. – Mat Nov 15 '11 at 15:44
  • @Mat: Yes, adding a `const char*` constructor seems to be the right thing to do. I'm curious still why there's no implicit conversion to `string`, though... – Kerrek SB Nov 15 '11 at 15:49
  • @Dani: Yes, true; I meant why the conversion isn't considered in the copy-initialization, or even in a function call (e.g. `f("hello")` also doesn't work). – Kerrek SB Nov 15 '11 at 15:51
  • @KerrekSB: I think its something about reference binding to temporary – Daniel Nov 15 '11 at 15:53
  • @Dani: got it, cHao found the answer. Only one level of implicit conversion is allowed. – Kerrek SB Nov 15 '11 at 23:10
2

A conversion operator will provide an implicit conversion from your type to some other type. For example:

class A
{
public:
  operator std::string () const { return "foo"; }
};

However, implicit conversions can be dangerous because it can be employed when you don't expect it to be employed and would prefer a compiler error. Therefore, it's often better to implement a convert constructor on the destination object, rather than a conversion operator on the source object.

class A
{ 
public:
  A(const std::string& rhs) { /* ... */ }
};

There is still some implicit conversion possible with this, however. Consider:

string f = "foo";
A foo = f;

f is implicitly converted to an A because the convert constructor is available. It might be best for this to result in a compiler error, however. By marking the convert constructor explicit, you make it so that you can easily convert from one type to another, but only when you really intended to.

#include <string>

using namespace std;

class A
{
public:
    A() {};
    explicit A(const std::string& ) {};
};

int main()
{
    string f = "foof";
    A a1 = f;   // WONT COMPILE
    A a(f);     // WILL COMPILE
}
Community
  • 1
  • 1
John Dibling
  • 99,718
  • 31
  • 186
  • 324
1

There's no need for casting ... this can easily be done by creating constructors for you class that take the correct argument-type, and only use a single argument so they can be called implicitly by the compiler.

If your constructors take more than one non-default argument, then they can't be used in conversion operations.

Furthermore, if you ever want to avoid ambiguity in the selection of constructors based on inferred type conversion, you can always use the explicit keyword with your constructors. For instance, imagine a scenario like:

 struct test
 {
     int a;

     test (signed int b): a(b) {}
     test (unsigned int b): a(b) {}
 };

 int main()
 {
     test A = 5.0;

     return 0;
 }

This will result in a compiler error due to ambiguity in the conversion of the double type to a scaler-type ... the double type could be implicitly converted to both types. This can be fixed through the use of the explicit keyword to-do the following:

struct test
{
    int a;

    test (signed int b): a(b) {}
    explicit test (unsigned int b): a(b) {}
};

Now the unsigned int version of the constructor for test will only be called if it's passed an unsigned int. A conversion operation from a double type will use the default int version, removing the ambiguity problem.

Jason
  • 31,834
  • 7
  • 59
  • 78
1

What it means in C# ?

In C#, the semantics is not about overloading the operator = (which you can't), but provide a cast operator from any type to the type A, or from the type A to any type.

This means the following code (completed with the A-to-type conversions):

class A {
    public static implicit operator A(string s) {
        return new A();
    }
    public static implicit operator A(double d) {
        return new A();
    }        
    public static implicit operator string(A a) {
        return string.Empty;
    }
    public static implicit operator double(A a) {
        return 0.0;
    }        
    static void Main(string[] args) {
        A a = "hello";    // line A
        A b = 5.0;        // line B
        a = "World";      // line C
        a = 3.14;         // line D
        double d = a ;    // line E
        string s = a ;    // line F
    }
}

works for assignment, either in simple assignments (like lines C and D) or assignment in declarations (like lines A and B). The line E and F demonstrate how we can convert from the user type to other types.

How to do it in C++ ?

In C++, the lines A and B are object constructions, which will call the relevant constructors.

The lines C and D are assignment, which will call the relevant assignment operator.

So the C++ code for the C# code you provided must provide both constructors and assignments for string and doubles, as follows:

class A
{
    public:
        A(const std::string & s) { /*...*/ }
        A(double d)              { /*...*/ }

        A & operator = (const std::string & s) { /*...*/ ; return *this ; }
        A & operator = (double d)              { /*...*/ ; return *this ; }

        // etc.
} ;

This way, you can have

void main()
{
    A a0 = "Hello" ;   // constructor
    A a1("Hello") ;    // constructor
    A a2 = 5.0 ;       // constructor
    A a3(5.0) ;        // constructor


    a0 = "Hello World" ; // assignment
    a0 = 3.14 ;          // assignment
}

What about cast operators in C++ ?

The cast operators in C++ work like the following C# converter operators:

class A
{
    static public operator string(A a)   { /*... ; return a string */ }
    static public operator double(A a)   { /*... ; return a double */ }

        // etc.
}

In C++, the cast operators are written as such:

class A
{
    public:
        operator std::string()   { /*... ; return a string */ }
        operator double()        { /*... ; return a double */ }

        // etc.
} ;

void main()
{
    A a ;
    std::string s ;
    double d ;

    // etc.

    s = a ;    // cast operator
    d = a ;    // cast operator
}

Why is it so complicated in C++ ?

In C#, a cast/conversion operator can be written from the class type to any type, or from any type to the class type.

In C++, for conversion, you must choose between one or more ways, that is: constructor, assignment operator or cast operator, because in C++, you have a fine grained control on types and operation, and thus, you must use that fine grained API.

Constructions means the object does not exist, so there is no point in constructing it to some default value and then assigning it another value (this can cost in speed). In C#, the reference/value type is null/zeroed before the assignment, so this is not an issue.

Assignment means the object does already exist, so it is possible one can reuse the same internals to accommodate the new value. In C#, the original reference object is discarded, and another is created (if the creation costs, that you'll pay that price)

As for the cast, it is used in the current case to convert an existing class into another for which you have no control: You have no right to extend the std::string to accommodate a constructor for your class, and there is no way to add a constructor to a built-in type like double.

paercebal
  • 81,378
  • 38
  • 130
  • 159
0

You can provide a constructor with one argument of your desired type:

class Foo {

  int bar;

public:
  Foo(const int bar) : bar(bar) {};

}

Foo foo = 5;
Matthias Meid
  • 12,455
  • 7
  • 45
  • 79