Making auto_cast safe

2020-05-26 10:42发布

问题:

GMan has posted a code for the delicious auto_cast “operator” that allows to write code such as the following in C++:

float f = 4.0f;
int i = auto_cast(f);
// instead of:
int j = static_cast<int>(f);

or, more saliently,

T x = value;
typename nested_type<with, template_arguments>::type y = auto_cast(x);
// instead of
typedef typename nested_type<with, template_arguments>::type my_type;
my_type z = static_cast<my_type>(x);

Basically, the operator is great in removing unnecessary redundancy from a static_cast, and at the same time is still safe. It’s even more safe than static_cast since it prevents accidentally mismatching the types:

int i = 1234;
short s = static_cast<char>(i); // s == -46, not 1234!

However, j_random_hacker has noticed a flaw in the operator:

static_cast allows downcasts, which are potentially unsafe.

Indeed, an auto_cast should probably forbid downcasts because they may fail:

class base { };
class derived : public base { };

base b;
derived* pd = auto_cast(&b); // should fail at compile time.

Hence my question:

How would you modify the auto_cast implementation in order to forbid downcasts? This will probably involve enable_if. I’m especially interested in a solution that allows the compiler to provide good diagnostics in case of failure (= readable error messages).

回答1:

It appears you want to use the T{u} form of initialization.

template <typename U>
operator U()
{
    return U{std::forward<T>(mX)};
}

One of the reasons for these uniform initialization was that to use explicit constructors for creating a temporary, you need a cast aka T(u). With T{u} that problem was solved. For C++03, I imagine you could do something like this:

template<typename T>
struct construct_explicit {
  template<typename U>
  construct_explicit(U &u):t(u) { }
  template<typename U>
  construct_explicit(U const &u):t(u) { }

  T &get() { return t; }
  T const& get() const { return t; }

  T t;
};

Then you can say construct_explicit<U>(mX).get(), although in a case like in your conversion function, it also works to use a named variable as an intermediary step, I think

template <typename U>
operator U()
{
    // or C++03: U u(mX);
    U u(std::forward<T>(mX));
    return u;
}


回答2:

You could use type-traits to disable the operator if T is a base of R. As we're in C++0x, you can explicitly static_assert(std::is_base_of<T, U>::value, "Cannot auto_cast downwards!");



回答3:

I wouldn't even use auto_cast because static_cast, const_cast, dynamic_cast and reinterpret_cast are also made ugly by design to help pointing code that could need refactoring : a ugly operation should have an ugly look.

A secondary reason for introducing the new-style cast was that C-style casts are very hard to spot in a program. For example, you can't conveniently search for casts using an ordinary editor or word processor. This near-invisibility of C-style casts is especially unfortunate because they are so potentially damaging. An ugly operation should have an ugly syntactic form. That observation was part of the reason for choosing the syntax for the new-style casts. A further reason was for the new-style casts to match the template notation, so that programmers can write their own casts, especially run-time checked casts.

http://www2.research.att.com/~bs/bs_faq2.html#static-cast

I prefer to see clearly in the code where it could be better, or where we explicitely need to make this ugly operation.



标签: c++ casting