Why Is "Operator Void" Not Invoked with Cast Syntax

Why is operator void not invoked with cast syntax?

The technical reason why is found in §12.3.2:

A conversion function is never used to convert a (possibly cv-qualified) object to the (possibly cv-qualified) same object type (or a reference to it), to a (possibly cv-qualified) base class of that type (or a reference to it), or to (possibly cv-qualified) void.

The rationale is (likely) to allow §5.2.9/4 to work:

Any expression can be explicitly converted to type “cv void.” The expression value is discarded.

(void)expr to suppose to do nothing for the resulting value of any expression, but if it called your conversion operator it wouldn't be discarding anything. So they ban the use of operator void in conversions.


Why not make it ill-formed to have the conversion-type-id be void? Who knows, but keep in mind it's not totally useless:

struct foo
{
operator void()
{
std::cout << "huh?" << std::endl;
}

};

typedef void (foo::*void_function)();

foo f;
void_function func = &foo::operator void;

(f.*func)(); // prints "huh"
f.operator void(); // also does (which you knew)

It is still technically potentially useful for something, so maybe that's rationale enough not to make it ill-formed.

Why doesn't new require a cast to the pointer even though malloc requires it?

Because when you're using new, you (normally) use a "new expression", which allocates and initializes an object. You're then assigning the address of that object to a pointer to an object of the same (or parent) type, which doesn't require a cast. A normal new expression (i.e., not a placement new) will invoke operator new internally but the result of the new expression is not just the result from operator new.

If you invoke operator new directly, then you need to cast its result to assign the return value to a non-void pointer, just like you have to do with the return from malloc.

Why operator void*() conversion function added to the C++ stream classes?

A feature of std::stringstream is that it is intended that if the stream is used as a bool, it gets converted to true if the stream is still valid and false if it's not. For instance this lets you use a simple syntax if you implement your own version of lexical cast.

(For reference, boost contains a template function called lexical_cast which does something similar to the following simple template function.)

template <typename T, typename U>
T lexical_cast(const U & u) {
T t;
std::stringstream ss;
if (ss << u && ss >> t) {
return t;
} else {
throw bad_lexical_cast();
}
}

If for instance, you work on a project where exceptions are disallowed, you might want to roll the following version of this instead.

template <typename T, typename U>
boost::optional<T> lexical_cast(const U & u) {
T t;
std::stringstream ss;
if (ss << u && ss >> t) {
return t;
} else {
return boost::none;
}
}

(There are various ways the above could be improved, this code is just to give an example.)

The operator void * is being used in the above as follows:

  1. ss << u returns a reference to ss.
  2. The ss is implicitly converted to void * which is nullptr if the stream operation failed, and non-null if the stream is still good.
  3. The && operator aborts quickly, depending on the truth value of that pointer.
  4. The second part ss >> t runs, and returns and is also converted to void *.
  5. If both operations succeeded then we can return the streamed result t. If either failed then we signal an error.

The bool conversion feature is basically just syntactic sugar that allows this (and many other things) to be written concisely.

The drawback of actually introducing an implicit bool conversion is that then, std::stringstream becomes implicitly convertible to int and many other types also, because bool is implicitly convertible to int. This ultimately causes syntactic nightmares elsewhere.

For instance, if std::stringstream had an implicit operator bool conversion, suppose you have this simple code:

std::stringstream ss;
int x = 5;
ss << x;

Now in overload resolution, you have two potential overloads to consider (!), the normal one operator<<(std::stringstream &, int), and the one in which ss is converted to bool, then promoted to int, and the bit shift operator may apply operator<<(int, int), all because of the implicit conversion to bool...

The workaround is to use an implicit conversion to void * instead, which can be used contextually as a bool, but isn't actually implicitly convertible to bool or int.

In C++11 we don't need this workaround any more, and there's no reason that anyone would have explicitly used the void * conversion, so it was just removed.

(I'm searching for a reference, but I believe that the value of this function was only specified up to when it should be nullptr vs. when it should not be nullptr, and that it was implementation defined what nonzero pointer values it might yield. So any code that relied on the void * version and cannot be trivially refactored to use the bool version would have been incorrect anyways.)

The void * workaround is still not without problems. In boost a more sophisticated "safe bool" idiom was developed for pre-C++11 code, which iiuc is based on something like T* where T is either a "type-used once", like a struct which is defined in the class implementing the idiom, or using a pointer-to-member function for that class and using return values which are either a particular private member function of that class, or nullptr. The "safe bool" idiom is used for instance in all of the boost smart pointers.

Regardless this whole mess was cleaned up in C++11 by introducing explicit operator bool.

More info here: Is the safe-bool idiom obsolete in C++11?

Cast instance to void?

The "cast to void", however it is spelled, is a discarded value expression. It does not constitute a conversion, and therefore does not consider conversion functions.

C++ allows you to do lots of things that are pointless; it would be harder to forbid some special cases than to just leave the rules general.

Why does the type cast of malloc in C have the * symbol on the right and not on the left?

That's because when declaring a pointer the "*" is on the left, not on the right.

The * is on the right of the type name. In void *p = &a; type is void *, a void pointer. It is on the left of the thing it's being applied to, the variable p.

In ptr = (cast-type*) malloc(byte-size) the type is cast-type *, the * is to the right of the type name. The cast is on the left of the thing being cast, the call to malloc, like an adjective.

[I do find it odd that we write type *variable, which makes it seem like the * is part of the variable rather than type* variable which puts the * with the type.]

What does operator void* () mean?

No they are two different operators. The operator void* function is a type casting function, while operator() is a function call operator.

The first is used when you want to convert an instance of Foo to a void*, like e.g.

Foo foo;
void* ptr = foo; // The compiler will call `operator void*` here

The second is used as a function:

Foo foo;
void* ptr = foo(); // The compiler calls `operator()` here

Why does a direct cast fail but the as operator succeed when testing a constrained generic type?

I used this question as the basis for a blog article in October 2015. Thanks for the great question!

Could someone please explain why this is the case?

"Why" questions are hard to answer; the answer is "because that's what the spec says" and then the natural question is "why does the spec say that?"

So let me make the question more crisp:

What language design factors influenced the decision to make the given cast operator illegal on constrained type parameters?

Consider the following scenario. You have a base type Fruit, derived types Apple and Banana, and, now comes the important part, a user-defined conversion from Apple to Banana.

What do you think this should do when called as M<Apple>?

void M<T>(T t) where T : Fruit
{
Banana b = (Banana)t;
}

Most people reading the code would say that this should call the user-defined conversion from Apple to Banana. But C# generics are not C++ templates; the method is not recompiled from scratch for every generic construction. Rather, the method is compiled once, and during that compilation the meaning of every operator, including casts, is determined for every possible generic instantiation.

The body of M<Apple> would have to have a user-defined conversion. The body of M<Banana> would have an identity conversion. M<Cherry> would be an error. We cannot have three different meanings of an operator in a generic method, so the operator is rejected.

Instead what you have to do is:

void M<T>(T t) where T : Fruit
{
Banana b = (Banana)(object)t;
}

Now both conversions are clear. The conversion to object is an implicit reference conversion; the conversion to Banana is an explicit reference conversion. The user-defined conversion is never called, and if this is constructed with Cherry then the error is at runtime, not compile time, as it always is when casting from object.

The as operator is not like the cast operator; it always means the same thing no matter what types it is given because the as operator does not ever invoke a user-defined conversion. Therefore it can be used in a context where a cast would be illegal.

Why type inference and implicit operator is not working in the following situations?

Your second question is why the implicit conversion from ()=>"" to V<string> does not succeed, even though ()=>"" is convertible to Func<string> and Func<string> is convertible to V<string>.

Again, I do not know how to answer "why not?" questions but I do know how to answer the question "what line of the specification indicates that the compiler ought to reject this code?" The relevant line is:

First, if required, performing a standard conversion from the source type to the operand type of the user-defined or lifted conversion operator.

Note that there is a small error here; this should say performing a standard conversion from the source expression. The source expression may not have a type. I believe I gave that note to the spec maintainer before I left the team, so hopefully this will get fixed in the next edition.

Anyway, it should now be clear what is going on here. There is no standard conversion from a lambda to a delegate type, and therefore the user-defined conversion is classified as inapplicable by the conversion resolution algorithm.



Related Topics



Leave a reply



Submit