views:

539

answers:

4

Kind of a random question...

What I'm looking for is a way to express a cast operation which uses a defined operator of the class instance I'm casting from, and generates a compile-time error if there is not a defined cast operator for the type. So, for example, what I'm looking for is something like:

template< typename RESULT_TYPE, typename INPUT_TYPE >
RESULT_TYPE operator_cast( const INPUT_TYPE& tValue )
{
    return tValue.operator RESULT_TYPE();
}

// Should work...
CString sString;
LPCTSTR pcszString = operator_cast< LPCTSTR >( sString );

// Should fail...
int iValue = 42;
DWORD dwValue = operator_cast< DWORD >( iValue );

Interesting side-note: The above code crashes the VS2005 C++ compiler, and doesn't compile correctly in the VS2008 C++ compiler due to what I'm guessing is a compiler bug, but hopefully demonstrates the idea.

Anybody know of any way to achieve this effect?

Edit: More rationale, to explain why you might use this. Say you have a wrapper class which is supposed to encapsulate or abstract a type, and you're casting it to the encapsulated type. You could use static_cast<>, but that might work when you wanted it to fail (ie: the compiler chooses an operator which is allowed to convert to the type you asked for, when you wanted a failure because that operator is not present).

Admittedly it's an uncommon case, but it's annoying that I can't express exactly what I want the compiler to do in an encapsulated function... hence the question here.

+1  A: 

Using a converting constructor marked explicit is how you would prevent the compiler from allowing implicitly converted types from initializing your wrapper class.

Greg Rogers
Yes, but what about casting to built-in intrinsic types where I cannot add an explicit constructor, or modify the conversion behavior?
Nick
You mean you want to change the default casting behavior between two intrinsic types?
Greg Rogers
No, I'm converting from the wrapper class to an intrinsic type, so I cannot tell the compiler that I want the intrinsic type to only be explicitly constructed (and that would be bad in the general sense also).
Nick
A: 

sounds like you want template specialization, something like this would do:

/* general template */
template<typename T1, typename T2> T1 operator_cast(const T2 &x);

/* do this for each valid cast */
template<> LPCTSTR operator_cast(const CString &x) { return (LPCTSTR)x; }

EDIT: As noted in another post, you can put something in the general version to give you a more useful error message if an unsupported cast is performed.

Evan Teran
+1  A: 

As template-related compiler error messages are usually a complete pain to unravel, if you don't mind specifying each conversion you can get the compiler to emit a more instructive message in the fail case by providing a default template definition too. This uses the fact that the compiler will only attempt to compile code in templates that is actually invoked.

#include <string>

// Class to trigger compiler warning   
class NO_OPERATOR_CONVERSION_AVAILABLE
{
private:
   NO_OPERATOR_CONVERSION_AVAILABLE(){};
};

// Default template definition to cause compiler error
template<typename T1, typename T2> T1 operator_cast(const T2&)
{
   NO_OPERATOR_CONVERSION_AVAILABLE a;
   return T1();
}

// Template specialisation
template<> std::string operator_cast(const std::string &x)
{
   return x;
}
nice technique to get a more useful error out of it.
Evan Teran
This would work, with the only downside being the need to explicitly define each "valid" case instead of writing one generic version. I think I'd call this version allowed_cast to better capture the effect.
Nick
Yes, I think that semantic difference is valid. There must be a way to determine at compile time if an explicit conversion operator applies - I can think off the top of my head how you can tell if *any* conversion is valid, but explicit is harder. I will think some more...
+3  A: 

The code you posted works with the Cameau compiler (which is usually a good indication that it's valid C++).

As you know a valid cast consists of no more than one user defined cast, so a possible solution I was thinking of was adding another user defined cast by defining a new type in the cast template and having a static assert that no cast is available from the new type to the result type (using boost is_convertible), however this doesn't distinguish between cast operators and cast constructors (ctor with one argument) and alows additional casts to take place (e.g. void* to bool). I'm not sure if making a distinction between cast operators and cast constructors is the the correct thing to do but that's what the question states.

After a couple of days mulling this over it hit me, you can simply take the address of the cast operator. This is slightly easier said than done due to C++'s hairy pointer to member syntax (it took me way longer than expected to get it right). I don't know if this works on VS2008, I only checked it on Cameau.

template< typename Res, typename T>
Res operator_cast( const T& t )
{
    typedef Res (T::*cast_op_t)() const;
    cast_op_t cast_op = &T::operator Res;
    return (t.*cast_op)();
}

Edit: I got a chance to test it on VS2005 and VS2008. My findings differ from the original poster's.

  • On VS2008 the original version seems to work fine (as does mine).
  • On VS2005 the original version only crashes the compiler when casting from a built in type (e.g. casting int to int) after providing a compilation error which doesn't seem so bad too me and my version seems to works in all cases.
Motti
Did you try your template with the two examples? VS2008 didn't compile the one which should have worked, although it did compile the template itself just fine.
Nick
I just tried it, and you're solution seems to work well. I get a compiler error on VS2005 for built-in types on the initial typedef, which is excellent. Nice idea. :)
Nick