devxlogo

Use Explicit Conversion Functions to Avert Reckless Implicit Conversions

Use Explicit Conversion Functions to Avert Reckless Implicit Conversions

he compiler invokes user-defined conversion functions (which are also called conversion operators) implicitly. In most cases, this process is well-behaved and intended. However, there are cases when you certainly don’t want the compiler to invoke the conversion operator implicitly—but you can’t prevent it. Several workarounds have been devised to mitigate this problem, including the indirect conversion idiom. And yet, these workarounds are neither intuitive nor perfect. C++0x finally brings a radical solution in the form of explicit conversion functions.


Your class defines a conversion function to a certain type. You want to ensure that this function will be invoked only when appropriate.


Declare conversion functions explicit, thus forcing clients to use casts explicitly.

Presenting the Problem
The problematic nature of unrestrained conversion functions is clearly demonstrated in smart pointers. Class std::shared_ptr defines an implicit conversion to an implementation-defined Boolean type. As you may recall, the “implementation-defined Boolean type” is actually a pointer to a data member, not the built-in booltype:

template class Ptr{private: struct PtrConversion {  int valid; };public: operator PtrConversion::*pmi() const//Boolean conversion {  return rawptr? &PtrConversion::valid : 0; }};

As ugly as this kludge may seem, it does prevent awful bugs like these:

Ptr p1, p2;cout pf;Ptr  pq; //Query and File are unrelated typesif(pf==pq)... //compares raw pointers, not objects!

There are, however, other ailments that the indirect conversion idiom cannot cure.

Ambiguity Issues
Suppose you’re designing a simple string class. Naturally, your class defines a conversion functions to char * and an overloaded [] operator. Each of these functions has a const and a non-constversion:

class BuggyString { char buf[100];public: BuggyString(const char* s); //subscripting char& operator[] (size_t n); const char& operator[] (size_t n) const;//implicit conversion to char* operator char* (); operator const char* () const;};

When some compilers compile the following code, they issue a compilation error:

BuggyString str("buggi");str[4] = 'y'; //error, overload resolution ambiguity

Where does this error come from? When the compiler sees the expression str[4], it looks up the definition of str and discovers that str isn’t an array. At this stage, there are two possible ways to interpret the expression str[4]:

  • Invoking the overloaded [] member function.
  • Invoking BuggyString::operator char *, and then applying the built-in [] operator to the result.

In other words, depending on the underlying type of std::ptrdiff_t (which affects overload resolution) the compiler is faced with two options: either interpret str[4]as:

str.operator[](4) //call overloaded []

or interpret it as:

str.operator char*()[4]//call operator char* +built-in []

In C++03, the common approach is to avoid conversion functions in the first place. The std::string class, for example, defines the member function string::c_str() as a substitute for operator const char *. This approach works well when the destination type is known at compile time. However, when templates are involved, named functions are problematic because the destination type can only be determined when you instantiate a template.

Explicit Conversion Functions
You’re already familiar with the use of explicit in constructors that take a single argument. Unless declared explicit, a constructor C(arg) operates as implicit conversion functions that converts arg to C. C++0x extends the use of explicit to conversion functions as well. When you declare a conversion function as explicit, implicit conversions are permitted only in direct initialization expressions:

class BuggyString {//now explicit explicit operator char*(); explicit operator const char* () const;};const char * ps(str);//OK, direct initialization ps=str; //Error, explicit cast requiredps=(const char *) str; //OK, C-style cast ps=static_cast(str); //OK, new style cast

Safe and Sound
The use of explicitguarantees that conversion functions do not surprise you with undesirable conversions. Instead, every conversion operation requires an explicit permission from you in the form of at cast notation (except in the case of direct initializations). Explicit conversion functions thus offer a safe and uniform method for conversions.

devx-admin

Share the Post: