user1502952
user1502952

Reputation: 1420

What is the significance of conversion function (C++)

I came across the concept of conversion function. This is the sample code which uses both getter and conversion function.

#include<iostream>

using namespace std;

class conv
{
  int val;
  int a,b;

  public:

  conv(int x, int y)
  {
    a = x;
    b = y;
    val = 1;
    val = a*b;
  }

  int get(){ return val; };

  operator int(){ return val; };

};

int main()
{
  conv obj(1,2);
  int x;

  x = obj; // using conversion function

  cout<<"Using conversion function"<<x;

  cout<<"Using getters"<<obj.get();

  return 0;
 }

Both cout statements produce same output. I would like to know what is the special significance of conversion function over getter function as the same could be achieved by getter function?

Thanks.

Upvotes: 1

Views: 240

Answers (5)

getters/setters and conversion operators serve different purposes:

  1. Accessors serve to make access to a private member public. By defining only one, you can achieve either public write or read access without making the other one public.

  2. Conversion operators serve to convert the entire object to a different type.

IMHO, you should not be doing either frequently: Accessors are a code smell, that the class is nothing more than a data structure, which is not OOP (even though most people don't realize that they are actually defining a public member).

Conversion operators have the problem that they make the code less easy to follow, because the conversion is not visible in the calling code. And, in conjunction with conversion constructors (constructors taking only one argument), they can easily lead to tons of "conversion is ambigous" compiler errors.

Generally, the only conversion operator you ever want to write is the conversion to bool, answering the question whether the instance is fundamentally usable. That way you can write code like this:

if(myInstance) {
    //do something sensible
} else {
    //handle error
}

Upvotes: 2

Tom
Tom

Reputation: 2389

The main purpose of the conversion operator (without the explicit keyword) is to provide implicit conversion of your type to a target type. This means that (for example) if you have a function which takes the target type (in your example, an int):

void function (int value) {...}

then you can pass an instance of your type, and the compiler will implicitly call the conversion operator in order to call the function:

// your code:
conv obj(1,2);
function(obj); // compiler will call conv::operator int()

On most projects I've worked on, use of these is frowned upon, due to the fact that using this feature of C++ makes your code harder to follow.

The explicit keyword informs the compiler to not allow the implicit conversion (pre-C++11, this only applied to conversion constructors). In this case, you have to explicitly cast the object:

// your code:
conv obj(1,2);
function(static_cast<int>(obj)); // compiler will call conv::operator int()

Upvotes: 2

Manu343726
Manu343726

Reputation: 14174

First of all, conversion operators provides a less verbose sintax. I'm the only person who hates the Java get/set syntax?
Bt the main purpose of this feature is to provide conversion between your own type and a specified type. Thats very useful in situations like:

  • Conversions to basic numeric types are natural and useful. For example:

    class fixed_point
    {
    private:
        long long int _bits;
    
    public:
        fixed_point(float number); //Cast from floating-point
        operator float(); //Cast to floating-point
    };
    

    Note that this feature could be easily overused, relying in confusing and non-natural cases. For example:

    struct complex_number
    {
        float real , imaginary;
    
        operator float(); //WTF!!! What is this? Real part? Imaginary part?
    };
    

    In fact, anyone could think that the "useful" case could be more readable using the named constructor idiom and a getter:

    class fixed_point
    {
    private:
        long long int _bits;
    
        fixed_point(long long int bits) _bits( bits ) {}
    
    public:
        static fixed_point from_floating_point(float number);
               float       to_floating_point();
    };
    
  • Object state flags: Provide to boolean conversions to check the state (OK vs ERROR) of an object. For example, that is what Standard Library streams do:

    while( cin >> input )
    {
        ...
    }
    

    That works because that streams implement operator>> in a way to implement a fluent interface, that is, allows concatenated operations like:

    cout << "hello" << "world";
    

    The implementation of stream operators returns a reference to the same stream which is passed into the operator, that is:

    istream& operator>>(istream& is , INPUT_TYPE input)
    {
        /* read ops */
        return is;
    }
    

    So when you do while(std::cin >> input) the sentence cin >> input is a call to an overload of operator>> which returns a reference to std::cin, and that object (cin) is implicitly casted to a boolean, to check the state of cin after the read operation.

C++11 provides explicit conversion operators which disallows that potentially confusing implicit conversions. That conversions are only allowed if the programmer specifies it in a explicit manner.

Finally, the C++ FAQ has a post with an awesome description, rules, and techniques to follow when overloading operators in C++, conversion operators included.

Upvotes: 1

Mats Petersson
Mats Petersson

Reputation: 129314

In this particular case, it's pretty useless.

I recently wrote some code to manipulate pixels, and it uses a conversion function to make 16-bit pixels out of a 8-bits each R, G, B, A value.

class Pixel
{
    uint8_t a, r, g, b;

public:
    Pixel() : a(0xff), r(0), g(0), b(0) {};
    Pixel(uint8_t aA, uint8_t aR, uint8_t aG, uint8_t aB) : a(aA), r(aR), g(aG), b(aB) {}
    Pixel(uint16_t rgb16) 
        {
            a = 0xff;
            r = ((rgb16 >> 11) & 31) << 3;
            g = ((rgb16 >> 5)  & 63) << 2;
            b = (rgb16 & 31) << 3;
        }

    Pixel(uint32_t argb32)
        {
            a = argb32 & 0xff;
            r = (argb32 >> 8) & 0xff;
            g = (argb32 >> 16) & 0xff;
            b = (argb32 >> 24) & 0xff;
        }
    uint8_t A() const { return a; }
    uint8_t R() const { return r; }
    uint8_t G() const { return g; }
    uint8_t B() const { return b; }

    void A(uint8_t aA) { a = aA; }
    void R(uint8_t aR) { r = aR; }
    void G(uint8_t aG) { g = aG; }
    void B(uint8_t aB) { b = aB; }

    operator uint16_t() 
        { 
            return ((r >> 3) << 11) |
                ((g >> 2) << 6) |
                (b >> 3); 
        }
};

which allows the code that uses this code to work like this:

uint16_t *frame_buffer;
...

for(...)
{
     ... 
     Pixel blended = Blend(above, below);
     frame_buffer[y * ScreenWidth + x] = blended;
}

Upvotes: 1

Vittorio Romeo
Vittorio Romeo

Reputation: 93264

It's less verbose. And potentially more confusing.

Upvotes: 0

Related Questions