3

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.

Ferruccio
  • 98,941
  • 38
  • 226
  • 299
Nick
  • 6,808
  • 1
  • 22
  • 34

4 Answers4

3

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
  • 110,860
  • 49
  • 189
  • 262
  • 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 Oct 22 '08 at 22:24
  • 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 Oct 22 '08 at 22:29
1

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

Community
  • 1
  • 1
Greg Rogers
  • 35,641
  • 17
  • 67
  • 94
  • Yes, but what about casting to built-in intrinsic types where I cannot add an explicit constructor, or modify the conversion behavior? – Nick Oct 16 '08 at 20:21
  • You mean you want to change the default casting behavior between two intrinsic types? – Greg Rogers Oct 16 '08 at 20:38
  • 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 Oct 16 '08 at 21:54
1

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;
}
user23167
  • 487
  • 5
  • 12
  • 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 Oct 16 '08 at 21:31
  • 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... – user23167 Oct 16 '08 at 21:38
0

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
  • 87,561
  • 32
  • 179
  • 238