joaocandre
joaocandre

Reputation: 1745

Advantages of static_cast over explicit instantiation/construction?

Considering the following function template func declared within MyClass class template:

template < T >
class MyClass {
public:
    MyClass() = default;

    // ...

    template < typename oT >
    oT func() {
        return static_cast< oT >(_m);  // alternative, return oT(_m);
    }

    // ...

 protected:
    T _m;
};

Is there any advantage in calling static_cast< oT >(_m) over explicit instantiation i.e. oT(_m)?

Upvotes: 0

Views: 305

Answers (1)

user17732522
user17732522

Reputation: 76658

oT(_m) will allow many more conversions than is probably intended. If static_cast<oT>(_m) wouldn't be possible, then oT(_m) will also try a const_cast, static_cast followed by const_cast, reinterpret_cast and reinterpret_cast followed by const_cast.

For example if T is const int* and oT is double*, then oT(_m) will succeed, while static_cast<oT>(_m) will fail. But using the result from oT(_m) as if it was a pointer to a double will result in undefined behavior.

If T and oT are pointers to classes that is maybe even more dangerous, since oT(_m) will always compile, whether or not there is an inheritance relation between the two classes.

Even static_cast allows for some conversions that may not be intended. For example it can downcast pointers to classes.

Another alternative is simply return _m;. This is weaker than both variants above. It will only allow for copy-initialization. However that might be too weak depending on your use case. For example it will not use explicit constructors and conversion functions. On the other hand before C++17 it doesn't require oT to be move-constructible, which the first two variants do require before C++17.

So you will need to decide what casts are supposed to be allowed and which ones are supposed to fail.

However, nothing prevents a user from calling func<T>() to obtain the original type anyway and the user can then simply cast it however they like, so I am not sure what is gained here over a simple getter.

Even more so, the user can in either case call func<T&>() and will get a reference to the member, thereby making the protected somewhat pointless. Maybe the allowed types T need to be constrained via SFINAE/requires-clause.

Upvotes: 5

Related Questions