Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

The Explicit Conversion Operators Proposal

Last updated Jan 1, 2003.

Many of the new C++0X core features, including delegating constructors and nullptr are already finalized. However, there are other proposals in the standardization pipeline which are still being developed. One of them is N1592: a proposal to add explicit conversion operators to C++. Learn what the problem with C++98 conversion operators is, how the new proposal attempts to fix it, and why I’m not particularly enthusiastic about it.

Implicit Conversions: Convenient but Dangerous

As we already know, a conversion operator implicitly converts objects of its class type to the target type. For example, a smart pointer class often defines a conversion operator that allows users to use smart pointer objects in a context that requires bool:

template <class T>
class Ptr
{
// stuff
public:
 operator bool() const
 {
 return (rawptr ? true: false);
 }
private:
 T * rawptr;
};

Ptr<int> ptr( &var );
if(ptr ) //calls conversion operator
{
// the pointer is valid
}
//...

In this example, the automatic conversion to bool is well-behaved. However, once you permit an implicit conversion, you may find that it could make your compiler accept nonsensical code such as this:

Ptr<int> p1;
Ptr<double> p2;

cout << "p1 + p2 = " << p1 + p2 << endl; // prints 0, 1, or 2
Ptr<File> sp1;
Ptr<Query> sp2; // Query and File are unrelated types

if (sp1 == sp2) // Converts both pointers to bool and compares results

The upshot is that while conversion operators are handy, they compromise type safety and disarm the compiler of its type-safety checks. For this reason, library designers either forgo conversion operators at the cost of inconveniencing users (the lack of a const char * conversion operator in std::string is a classic example of this), or they resort to embarrassingly contrived constructs to support this notion without its dangerous consequences.

One such contrived construct consists of replacing the target type bool with a pointer to a data member of an embedded struct:

template <class T>
class Ptr
{
public:
struct PointerConversion
{
 int valid;
};
typedef int PointerConversion::* datamemptr;

operator datamemptr ()const //a tamed version of operator bool
{
 return rawptr? &PointerConversion::valid : 0;
}
};

If you find this code awkward and unreadable, I agree. I don’t know how many C++ programmers can decipher pointer to data members syntax. Even for those who can, the idea of using a pointer to a data member of an embedded ad hoc struct for the purpose of restricting an implicit conversion to bool would sound odd, to say the least. However, such code does get written. The boost::shared_ptr class uses a similar implementation to support an implicit conversion to bool which is free from unintended conversion to arithmetic types.

Converting Constructors

Non-explicit constructors taking one argument cause a similar problem. Every such constructor of a class called X functions as an implicit conversion operator, converting an argument of type Y to X:

class U{}; 
class T
{
public:
T( U const & ); // implicitly converts U to T
};

U u;
T t;
t=u; //u implicitly converted to T

In the case of converting constructors, you can disable implicit conversions by declaring the constructor as explicit The N1592 proposal stretches the semantics of this keyword to all conversion operators. A conversion operator declared explicit will not perform an implicit conversion. Instead, the programmer will have to call it explicitly:

class U{}; class S{};
class T{
public:
 operator U { return u;}//implicit
 explicit operator S {return S();}
private:
 U u;
};

T t; 
U u;
S s;
u=t; //OK, implicit conversion operator used
u=s; //error, no implicit conversion from S to U
u=static_cast<U&> (s); //OK, explicit conversion used
u= (U)s; //OK, C-style cast

An explicit conversion operator is merely a uniform naming convention that does the same job of an older concept, namely: a member function that performs an explicit conversion. In other words, instead of calling string::c_str() to convert a string object to const char *, the proposed feature allows you to replace c_str() with:

class MyString
{
public:
explicit operator const char *() const;
};

However, the explicit conversion operator offers one advantage over the traditional conversion functions. In generic programming, one often needs to convert objects whose type is unknown at coding time. An explicit conversion operator can be generic as well, using a parameterized destination type:

explicit operator const T *() const;

The proposal also includes another generic conversion idiom based on namespaces and a template member function with an agreed-upon name:

class M
{
public:
template <class To>
operator To()
{
 return static_cast<To>( *this );
}
};

Conclusions

In the Berlin meeting of April 2006, this proposal was accepted by the Evolution group and was moved to Core for "further scrutiny". However, if you look examine the carefully you will notice that this proposal is still far from final. There’s no doubt that the current semantics of conversion operators is too permissive. However, I believe that combining namespaces and an agreed-upon function name is wrong. Conversion operators were added to C++ to avoid unwieldy and complicated syntactic beasts. This proposal (i.e., the namespace based solution) makes conversion operators overly arcane and verbose. As for the proposed explicit conversion operators, e.g.,

explicit operator const T *() const;

They are slightly better since they are intuitive and simple to use. However, I suspect that once this construct becomes standardized, it will be overused by defensive programming zealots. Obviously, this will lead users to tamper with standard classes (e.g., deriving from concrete classes that lack a virtual destructor) only to override an explicit conversion operator with an implicit one. Perhaps boost::shared_ptr designers were right all along...