Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicit conversion from user-defined type to primitive type in C++

I am able to find plenty of information about implicit conversion from, say, an int to a user defined type. i.e. if a constructor takes an int as its parameter and is not prefaced by "explicit" then implicit conversions can occur.

What if I want my class to implicitly convert to an int?

For example, what function needs to be added either inside or outside of SimpleClass such that the main function will compile and output "1" to the console? (see comments)

#include <iostream>

class SimpleClass
{
private:
    int m_int;
public:
    SimpleClass(int value)
    : m_int(value) {}
};

int main(int argc, const char * argv[])
{
    SimpleClass simpleclass(1);
    int i = simpleclass; // does not complile
    std::cout << i << std::endl; // should output "1" to the console
    return 0;
}
like image 248
Matthew James Briggs Avatar asked Dec 28 '13 21:12

Matthew James Briggs


2 Answers

Implicit conversions can be defined in two ways:

  • non-explicit single-argument constructor.
  • non-explicit conversion function (a.k.a. conversion operator), N3337 12.3.2

The later allows defining conversion from class type to primitive type. Just add

class SimpleClass {
   // ...
   operator int() const;
};

SimpleClass::operator int() const
{
   return m_int;
}
like image 151
Jan Hudec Avatar answered Oct 25 '22 04:10

Jan Hudec


The technical.

Conversion to (almost) any type T can be performed by an operator T member function.

It is by default invoked implicitly, and if you declare it const it can also be invoked on a const object.

Thus:

struct MyType
{
    operator int() const { return 1; }
};

Problems…

Having a an implicit conversion to basic type allows free play for all the built-in operators, including

  • Arithmetic operators.
  • Boolean operators.
  • Relational operators.

So you better make sure that all this works the way you want.

And that can be a lot of work!

There are also potential problems with overload resolution for calls involving instances of your type.

In short, implicit conversion to int, or pointer, or any built-in type, usually costs more than it's worth.

An exception where it can be worthwhile is when a class is used a lot, in a library.

What you can do about it.

Avoid implicit conversion, but do offer explicit conversion.

The best general explicit conversion is, IMHO, a named member function.

An alternative is an operator T prefixed with the keyword explicit, which is supported for this use in C++11 and later (in C++03 it could only be used on constructors).

If you want output via << to behave as if an implicit conversion is performed, then just define an operator<<.

And similarly for other situations where an implicit conversion would appear to be a general solution: just define what's appropriate for that specific situation, and avoid introducing a general implicit conversion.


To provide implicit conversion to a built-in type and yet avoid the “free for all” for built-in operators, you can use a templatized type conversion, e.g. like this:

#include <iostream>

template< class A, class B > struct Is_t_;

template< class Type > struct Is_t_<Type, Type> { using T = void; };

template< class A, class B >
using If_is_ = typename Is_t_<A, B>::T;

struct Bad_string
{
    operator const char* () const { return "666!"; }
    Bad_string( char const* = 0 ) {}
};

auto operator==( Bad_string const&, Bad_string const& )
    -> bool
{ return true; }

struct Good_string
{
    template< class Type, class Enabled_ = If_is_<const char*, Type>>
    operator Type() const { return "42 :)"; }

    Good_string( char const* = 0 ) {}
};

auto operator==( Good_string const&, Good_string const& )
    -> bool
{ return true; }

#if defined( DO_GOOD )
    using String = Good_string;
#elif defined( DO_BAD )
    using String = Bad_string;
#else
#   error "Define either DO_GOOD or DO_BAD, please."
#endif

auto main() -> int
{
    String a, b;
    (void) (a == "alfalfa");        // Errs for Bad_string
    (void) (a + 1);                 // Compiles for Bad_string.
}

Ironically, when DO_GOOD is defined this code crashes the Visual C++ 2015 update 1 compiler, a so called “ICE” (Internal Compiler Error).

A workaround for that compiler is to instead define If_is_ as

template< class A, class B >
using If_is_ = std::enable_if_t< std::is_same<A, B>::value >;
like image 25
Cheers and hth. - Alf Avatar answered Oct 25 '22 06:10

Cheers and hth. - Alf