Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

[Proposal] noreturn_t

20 views
Skip to first unread message

Andrei Polushin

unread,
Oct 22, 2006, 2:09:10 PM10/22/06
to
Hi,

I propose to add a special type, std::noreturn_t, to indicate that
function does never return.

Actually, some compilers have the similar feature, known as either
__declspec(noreturn) or __attribute__ ((noreturn)), but it is used
primarily for optimization purposes and does not affect language.

I suggest using it for strict type checking too, so std::noreturn_t
is not an "attribute", but a type that affects a function signature.

In addition, I suggest the "throw" expression to return "noreturn_t"
instead of "void", see rationale and use case below.

Use cases
---------

Starting from the simplest problem, consider the following code,
which does not compile:

// never returns
void throw_out_of_range(size_type n, size_type m)
{
ostringstream os;
os << "value " << n << " is out of range [0," << m << ")";
throw out_of_range(os.str());
}

// throws if n is out of range [0,size)
reference at(size_type n)
{
if (0 <= n && n < size()) {
return data[n];
}
throw_out_of_range(n, size());
// *error: function "at" should return a value*
}

Yes, we can revert the "if" condition to avoid an error, but why we
need to? Anyway, another example (adapted from boost::lexical_cast):

// never returns
void throw_io_failure()
{
throw ios_base::failure("I/O failure");
}

// throws if I/O failed
char get(const char* prompt) {
char c;
if (!(cout << prompt && cin >> c)) {
throw_io_failure();
}
// *warning: potentially uninitialized variable returned*
return c;
}

Thus we have a spurious warning.

In the following case we will see no warning, where it should be:

int f()
{
abort();

// *no warning about unreachable code*
int n = 22;
return n;
}

More serious case, when the function returns, but nobody expects this:

void hello()
{
cout << "Hello, World!" << endl;
}

set_unexpected(hello);
unexpected();
// execution continues after call to unexpected ?

In the last case, we can know the problem at compile time, imagine
set_unexpected() will accept only handlers that never return:

namespace std
{
typedef noreturn_t (*unexpected_handler)();

unexpected_handler set_unexpected(unexpected_handler f);
}

void hello()
{
cout << "Hello, World!" << endl;
}

std::noreturn_t bye()
{
cout << "Bye, World!" << endl;
// *error: "bye" should never return*
}

set_unexpected(hello); // cannot cast to unexpected_handler
set_unexpected(bye); // ok


Compatibility
-------------

1. Existing C++ implementations that do not check for non-returning
functions, may declare a no-op type:

namespace std
{
typedef void noreturn_t;
}

2. The existing code will fail to compile if it takes the address of
function redeclared with std::noreturn_t return type:

typedef void (*handler)();
void abort_workaround() { abort(); }

handler h = &abort; // error: cannot convert
handler h = &abort_workaround; // ok: workaround

This incompatibility is intentional.

3. Similarly, set_unexpected() etc. will not accept address of
functions that return void, which is also intentional.

4. All control paths of a function that never returns should end
with a call to a function that never returns, or with a throw
statement. This is the direct intent.


Throw expression and noreturn_t
-------------------------------

Throw expression should return std::noreturn_t instead of void, and
the type noreturn_t should be convertible to bool:

namespace std
{
struct noreturn_t {
operator bool() { return false; }
};
}

This allows a Perl-like syntax for checking return value:

cin >> c || throw io_failure("Cannot read from cin");

(Idea inspired by recent posting of Alf P. Steinbach in c.l.c++.m:
http://groups.google.com/group/comp.lang.c++.moderated/browse_frm/thread/85b034283152115e/ee9b31479b8c1e0e?#ee9b31479b8c1e0e
)

The name "noreturn_t" is inspired by std::nothrow_t.


Conclusion
----------

Having noreturn_t, we achieve:

- better compile-time control path analysis,
- better runtime error checking syntax,
- easier optimizable code,

Is it useful? I'm waiting for your comments.


--
Andrei Polushin

---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std...@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]

Alberto Ganesh Barbati

unread,
Oct 22, 2006, 4:19:36 PM10/22/06
to
Andrei Polushin ha scritto:

>
> I propose to add a special type, std::noreturn_t, to indicate that
> function does never return.
>
> <snip>

>
> Conclusion
> ----------
>
> Having noreturn_t, we achieve:
>
> - better compile-time control path analysis,
> - better runtime error checking syntax,
> - easier optimizable code,
>
> Is it useful? I'm waiting for your comments.

Even if it's a change that might break backward compatibility, I like it
and find it useful. Just three comments:

1) I would not allow implementations to define std::noreturn_t as a
typedef to void. In fact I would explicitly disallow such possibility.
If we allow it, the code:

typedef std::noreturn_t (*doesnt_return)();
void set_handler(doesnt_return f);

void bye();
set_handler(bye);

might compile on a conforming compiler and not on another conforming
compiler.

2) About set_unexpected and set_terminate, I think that changing
signatures of existing library function is unacceptable. I would just
add overloads. That would rip a little hole in the system, but otherwise
it looks like a showstopper to me.

3) I am a bit concerned with the implicit conversion to bool. Although I
don't dislike the perl-like syntax, having an implicit conversion would
make the entire proposal pointless, because you might then use
noreturn_t in a lot more places than we actually want. Even the idiom
"implicit conversion to unspecified-bool-type" (as used tr1::shared_ptr,
for example) is not good enough, IMHO. I don't see any alternative
except to explicitly allow built-in operator || and && (and *only* them,
no overloads!) to take as their second operand an expression of type
noreturn_t.

Just my 2 eurocent,

Ganesh

Andrei Alexandrescu (See Website For Email)

unread,
Oct 22, 2006, 9:38:07 PM10/22/06
to
Andrei Polushin wrote:
> I propose to add a special type, std::noreturn_t, to indicate that
> function does never return.
>
> Actually, some compilers have the similar feature, known as either
> __declspec(noreturn) or __attribute__ ((noreturn)), but it is used
> primarily for optimization purposes and does not affect language.

There is precedent in the Cecil language as well (the "none" type).

> Throw expression should return std::noreturn_t instead of void, and
> the type noreturn_t should be convertible to bool:

Actually the type noreturn_t should be convertible to any other type.
That makes it the bottom of the type hierarchy, where it belongs.

I implemented and used such a type in existing C++, and found it
marginally useful. I personally don't think it's a compelling feature to
add to the language.


Andrei

Alberto Ganesh Barbati

unread,
Oct 22, 2006, 9:12:06 PM10/22/06
to
Andrei Alexandrescu (See Website For Email) ha scritto:

> Andrei Polushin wrote:
>> I propose to add a special type, std::noreturn_t, to indicate that
>> function does never return.
>>
>> Actually, some compilers have the similar feature, known as either
>> __declspec(noreturn) or __attribute__ ((noreturn)), but it is used
>> primarily for optimization purposes and does not affect language.
>
> There is precedent in the Cecil language as well (the "none" type).
>
>> Throw expression should return std::noreturn_t instead of void, and
>> the type noreturn_t should be convertible to bool:
>
> Actually the type noreturn_t should be convertible to any other type.
> That makes it the bottom of the type hierarchy, where it belongs.

Why? IMHO it should behave like void, in the sense that it should not be
convertible to any type (except, possibly, void itself) so that you
cannot put it by mistake into an expression. I would also disallow the
possibility to explicitly convert an expression to noreturn_t (something
that you can do with void).

> I implemented and used such a type in existing C++, and found it
> marginally useful. I personally don't think it's a compelling feature to
> add to the language.

It was marginally useful because you implemented it in existing C++! The
proposal is only meaningful if the compiler is able to exploit the added
information for:

>> - better compile-time control path analysis,
>> - better runtime error checking syntax,
>> - easier optimizable code,

BTW: I guess the OP meant to say "better *compile-time* error checking
syntax" not runtime. There's no syntax checking happening at runtime...

Ganesh

James Dennett

unread,
Oct 22, 2006, 9:40:02 PM10/22/06
to
Alberto Ganesh Barbati wrote:
> Andrei Alexandrescu (See Website For Email) ha scritto:
>> Andrei Polushin wrote:
>>> I propose to add a special type, std::noreturn_t, to indicate that
>>> function does never return.
>>>
>>> Actually, some compilers have the similar feature, known as either
>>> __declspec(noreturn) or __attribute__ ((noreturn)), but it is used
>>> primarily for optimization purposes and does not affect language.
>>
>> There is precedent in the Cecil language as well (the "none" type).
>>
>>> Throw expression should return std::noreturn_t instead of void, and
>>> the type noreturn_t should be convertible to bool:
>>
>> Actually the type noreturn_t should be convertible to any other type.
>> That makes it the bottom of the type hierarchy, where it belongs.
>
> Why? IMHO it should behave like void, in the sense that it should not be
> convertible to any type (except, possibly, void itself) so that you
> cannot put it by mistake into an expression. I would also disallow the
> possibility to explicitly convert an expression to noreturn_t (something
> that you can do with void).

Typewise, I suspect it should behave like the existing
"no return" part of the language, namely "throw".

return true?throw a:foo;

is currently legal, but not because throw a has a type
that converts to anything -- rather, because if the
expression has a value at all then it was foo that was
evaluated, and so its type is used.

-- James

John Nagle

unread,
Oct 23, 2006, 1:45:25 AM10/23/06
to
James Dennett wrote:
> Alberto Ganesh Barbati wrote:
>
>>Andrei Alexandrescu (See Website For Email) ha scritto:
>>
>>>Andrei Polushin wrote:
>>>
>>>>I propose to add a special type, std::noreturn_t, to indicate that
>>>>function does never return.

It's not a compelling feature, but if added, it needs to be
part of the declaration, not the implementation, where
the caller can see it.

Realistically, though, it doesn't have much optimization
value. Functions that don't return tend not to be called
many times. (You could potentially have a function
that never returned normally, but could throw an exception.
This might be useful when starting up some task which
normally runs forever but could fail, throw, and be restarted
by its caller.)

John Nagle
Animats

Andrei Polushin

unread,
Oct 23, 2006, 12:55:58 PM10/23/06
to
Alberto Ganesh Barbati wrote:
> 1) I would not allow implementations to define std::noreturn_t as a
> typedef to void. In fact I would explicitly disallow such possibility.
> If we allow it, the code [...] might compile on a conforming compiler

> and not on another conforming compiler.

Actually, I'm claiming for the same, but showing the possibility for
the user to compile his conforming code with non-conforming compiler.
Conforming compiler should define a distinct type.

> 2) About set_unexpected and set_terminate, I think that changing
> signatures of existing library function is unacceptable. I would just
> add overloads. That would rip a little hole in the system, but
> otherwise it looks like a showstopper to me.

Good. And deprecate overloads for void-returning handlers.


> 3) I am a bit concerned with the implicit conversion to bool. Although I
> don't dislike the perl-like syntax, having an implicit conversion would
> make the entire proposal pointless, because you might then use
> noreturn_t in a lot more places than we actually want. Even the idiom
> "implicit conversion to unspecified-bool-type" (as used tr1::shared_ptr,
> for example) is not good enough, IMHO. I don't see any alternative
> except to explicitly allow built-in operator || and && (and *only* them,
> no overloads!) to take as their second operand an expression of type
> noreturn_t.

I tend to agree to disallow implicit conversion, and it seems I am
following your idea with built-in operators in another post.


> Just my 2 eurocent,

Thank you.


--
Andrei Polushin

Andrei Polushin

unread,
Oct 23, 2006, 12:57:24 PM10/23/06
to
Alberto Ganesh Barbati wrote:

> Andrei Alexandrescu wrote:
>> Actually the type noreturn_t should be convertible to any other type.
>> That makes it the bottom of the type hierarchy, where it belongs.
>
> Why? IMHO it should behave like void, in the sense that it should not be
> convertible to any type (except, possibly, void itself) so that you
> cannot put it by mistake into an expression. I would also disallow the
> possibility to explicitly convert an expression to noreturn_t (something
> that you can do with void).

Andrei, I'm also surprised by your claim. I guess it results from
the special treating of /throw-expression/ in conditional operator
(as noted by James Dennett - see below), or could you elaborate?


> Andrei Alexandrescu wrote:
>> I implemented and used such a type in existing C++, and found it
>> marginally useful. I personally don't think it's a compelling feature
>> to add to the language.

I don't know how to implement it in existing C++, satisfying all the
use cases identified. In fact, I don't know how to completely satisfy
any of those use cases. You cannot avoid the spurious errors and
warnings mentioned, and you cannot generate correct errors and warnings
for incorrect code. You may invent a Perl-like syntax for non-returning
functions on the right side of ||, but not for /throw-expression/ in
its normal form. You may achieve a type safety for set_unexpected(),
and that's everything I can imagine.


Alberto Ganesh Barbati wrote:
> It was marginally useful because you implemented it in existing C++! The
> proposal is only meaningful if the compiler is able to exploit the added
> information for:
>
>>> - better compile-time control path analysis,
>>> - better runtime error checking syntax,
>>> - easier optimizable code,
>
> BTW: I guess the OP meant to say "better *compile-time* error checking
> syntax" not runtime. There's no syntax checking happening at runtime...

No-no, namely runtime error checking *syntax* - I was talking about
the very Perl-like syntax used to check runtime errors:

cin >> c || throw failure();

And I agree that the proposal should be taken as a whole, redeclaring
"void abort()" as "noreturn_t abort()" is marginally useful in itself.


James Dennett wrote:
> Typewise, I suspect it should behave like the existing
> "no return" part of the language, namely "throw".
>
> return true?throw a:foo;
>
> is currently legal, but not because throw a has a type
> that converts to anything -- rather, because if the
> expression has a value at all then it was foo that was
> evaluated, and so its type is used.

I wasn't aware of this secret knowledge :) And I know nobody using it.
But thank you, it really helps in that it's already in the standard:


5.16 Conditional operator [expr.cond]

<orginal>
2 If either the second or the third operand has type (possibly cv-
qualified) void, then the lvalue-to-rvalue (4.1), array-to-pointer
(4.2), and function-to-pointer (4.3) standard conversions are
performed on the second and third operands, and one of the
following shall hold:

- The second or the third operand (but not both) is a
/throw-expression/ (15.1); the result is of the type of the other
and is an rvalue.

- Both the second and the third operands have type void the result
is of type void and is an rvalue. [Note: this includes the case
where both operands are /throw-expressions/. - end note]
</orginal>


Looks like we can restate it like this:


<replacement>
2a If either the second or the third operand (but not both) has type
std::noreturn_t, then the lvalue-to-rvalue (4.1), array-to-pointer
(4.2), and function-to-pointer (4.3) standard conversions are
performed on the other operand, and the result is of the type of
the other and is an rvalue.

2b If both the second and the third operands have type void the result
is of type void and is an rvalue.
</replacement>


Now both statements are about types, no special treating for throw.

Continuing, we can express || and && in the following manner
(following the idea by Alberto Ganesh Barbati):


5.14 Logical OR operator:

If the second operand has type std::noreturn_t, the expression

first || second

is interpreted as conditional operator (5.16)

first ? second : first

but the first operand is evaluated only once. [Note: the result is
the type of the first operand after performing standard conversions
defined in 5.16, and is an rvalue. - end note]

5.15 Logical OR operator:

If the second operand has type std::noreturn_t, the expression

first || second

is interpreted as conditional operator (5.16)

first ? first : second

but the first operand is evaluated only once. [Note: the result is
the type of the first operand after performing standard conversions
defined in 5.16, and is an rvalue. - end note]


With that final note, we have more than desired.

First of all, I don't have to say that std::noreturn_t should be
convertible to bool (the proposal was criticized for that).

Next, we are allowed to obtain the result and check at the same time:

ostream& read(char& c)
{
return cin >> c || throw "Cannot read from cin";
}

FILE* open(const char* name)
{
return fopen(name, "r") || abort();
}


--
Andrei Polushin

Andrei Polushin

unread,
Oct 23, 2006, 12:55:41 PM10/23/06
to
John Nagle wrote:
>> Andrei Polushin wrote:
>> I propose to add a special type, std::noreturn_t, to indicate that
>> function does never return.
>
> It's not a compelling feature, but if added, it needs to be
> part of the declaration, not the implementation, where
> the caller can see it.

It's just a type, so yes, it affects both the declaration and the
definition.


> Realistically, though, it doesn't have much optimization
> value. Functions that don't return tend not to be called
> many times.

The proposal is not about optimization only. In fact, optimization
is the last goal in the list.


> (You could potentially have a function
> that never returned normally, but could throw an exception.
> This might be useful when starting up some task which
> normally runs forever but could fail, throw, and be restarted
> by its caller.)

Thus you discovered the another third possibility for a function
constrained by std::noreturn_t return type:

1. call to a function that never returns,
2. throw an exception,
3. run into the endless loop.


--
Andrei Polushin

Andrei Polushin

unread,
Oct 23, 2006, 1:18:45 PM10/23/06
to
Andrei Polushin wrote:
> 5.14 Logical OR operator:
>
> If the second operand has type std::noreturn_t, the expression
>
> first || second

Sorry for the typo, this should be written as:

5.14 Logical AND operator:

If the second operand has type std::noreturn_t, the expression

first && second

> is interpreted as conditional operator (5.16)
>
> first ? second : first
>
> but the first operand is evaluated only once. [Note: the result is
> the type of the first operand after performing standard conversions
> defined in 5.16, and is an rvalue. - end note]
>
> 5.15 Logical OR operator:
>
> If the second operand has type std::noreturn_t, the expression
>
> first || second
>
> is interpreted as conditional operator (5.16)
>
> first ? first : second
>
> but the first operand is evaluated only once. [Note: the result is
> the type of the first operand after performing standard conversions
> defined in 5.16, and is an rvalue. - end note]

--

Alberto Ganesh Barbati

unread,
Oct 23, 2006, 3:17:41 PM10/23/06
to
Andrei Polushin ha scritto:

> Alberto Ganesh Barbati wrote:
>
> No-no, namely runtime error checking *syntax* - I was talking about
> the very Perl-like syntax used to check runtime errors:
>
> cin >> c || throw failure();

Oh, sorry... I didn't get it.

> Continuing, we can express || and && in the following manner
> (following the idea by Alberto Ganesh Barbati):
>
> 5.14 Logical OR operator:

(AND)


>
> If the second operand has type std::noreturn_t, the expression
>
> first || second

(&&)


>
> is interpreted as conditional operator (5.16)
>
> first ? second : first
>
> but the first operand is evaluated only once. [Note: the result is
> the type of the first operand after performing standard conversions
> defined in 5.16, and is an rvalue. - end note]
>
> 5.15 Logical OR operator:
>
> If the second operand has type std::noreturn_t, the expression
>
> first || second
>
> is interpreted as conditional operator (5.16)
>
> first ? first : second
>
> but the first operand is evaluated only once. [Note: the result is
> the type of the first operand after performing standard conversions
> defined in 5.16, and is an rvalue. - end note]

I like defining || and && in terms of ?:. Good idea! That implicitly
means that if the second operand of || or && is std::noreturn_t then
overloads won't be considered: the built-ins are always chosen. That's good.

BTW, I recently read that gcc allows as an extension to write "a ? : b"
with the meaning of "a ? a : b" except that a is evaluated only once.
That looks surprisingly similar to your proposed wording ;-)

> With that final note, we have more than desired.
>
> First of all, I don't have to say that std::noreturn_t should be
> convertible to bool (the proposal was criticized for that).

Yup!

> Next, we are allowed to obtain the result and check at the same time:
>
> ostream& read(char& c)
> {
> return cin >> c || throw "Cannot read from cin";
> }
>
> FILE* open(const char* name)
> {
> return fopen(name, "r") || abort();
> }

Nice! At first I felt uncomfortable to allow built-in || and && to
return something that's not a bool, but... hey! that syntax looks great!

Just my opinion,

Ganesh

Andrei Alexandrescu (See Website For Email)

unread,
Oct 23, 2006, 8:01:54 PM10/23/06
to
Andrei Polushin wrote:
> Alberto Ganesh Barbati wrote:
>> Andrei Alexandrescu wrote:
>>> Actually the type noreturn_t should be convertible to any other type.
>>> That makes it the bottom of the type hierarchy, where it belongs.
>> Why? IMHO it should behave like void, in the sense that it should not be
>> convertible to any type (except, possibly, void itself) so that you
>> cannot put it by mistake into an expression. I would also disallow the
>> possibility to explicitly convert an expression to noreturn_t (something
>> that you can do with void).
>
> Andrei, I'm also surprised by your claim. I guess it results from
> the special treating of /throw-expression/ in conditional operator
> (as noted by James Dennett - see below), or could you elaborate?

The idea is more general; I stole it from the Cecil language, which has
a "none" type at the bottom of the type hierarchy, meaning that it is a
subtype of any other type. One nice intuition is that "any" (the most
general object - the union of all possible types) is at the top of the
type hierarchy, and therefore "none" (the void set - the intersection of
all possible types) is at the bottom.

The rationale is this: a function that never return could statically
claim it returns any type, because a return will never substantiate. As
many people have noticed, this particularity confers "none" remarkable
expressiveness.

>> Andrei Alexandrescu wrote:
>>> I implemented and used such a type in existing C++, and found it
>>> marginally useful. I personally don't think it's a compelling feature
>>> to add to the language.
>
> I don't know how to implement it in existing C++, satisfying all the
> use cases identified. In fact, I don't know how to completely satisfy
> any of those use cases. You cannot avoid the spurious errors and
> warnings mentioned, and you cannot generate correct errors and warnings
> for incorrect code. You may invent a Perl-like syntax for non-returning
> functions on the right side of ||, but not for /throw-expression/ in
> its normal form. You may achieve a type safety for set_unexpected(),
> and that's everything I can imagine.

Of course you can't do in a library what can be done in the language. My
implementation goes like:

struct None {
None() { throw "can't happen"; }
None(const None&) {}
template <class T> operator T&() const {
throw "can't happen";
return *this;
}
};

> Alberto Ganesh Barbati wrote:
>> It was marginally useful because you implemented it in existing C++! The
>> proposal is only meaningful if the compiler is able to exploit the added
>> information for:
>>
>>>> - better compile-time control path analysis,
>>>> - better runtime error checking syntax,
>>>> - easier optimizable code,
>> BTW: I guess the OP meant to say "better *compile-time* error checking
>> syntax" not runtime. There's no syntax checking happening at runtime...
>
> No-no, namely runtime error checking *syntax* - I was talking about
> the very Perl-like syntax used to check runtime errors:
>
> cin >> c || throw failure();
>
> And I agree that the proposal should be taken as a whole, redeclaring
> "void abort()" as "noreturn_t abort()" is marginally useful in itself.

As an aside, I disagree with the idea of making "none" an exception for
operators && and ||. Instead, a "none" type that converts to anything
could work in conjunction with operators || and && if the language
preserved the type of the operators: operator|| would return its first
argument (whatever its type is) if it evaluates to nonzero, and its
second argument otherwise. Similarly, operator&& would return its first
argument if it does NOT evaluate to nonzero, and its second argument
otherwise.

But, while sensible, these all are changes that are too far-reaching and
with too little benefits to be considered seriously.


Andrei

Andrei Polushin

unread,
Oct 24, 2006, 1:24:46 PM10/24/06
to
Andrei Alexandrescu wrote:
> The idea is more general; I stole it from the Cecil language, which has
> a "none" type at the bottom of the type hierarchy, meaning that it is a
> subtype of any other type. One nice intuition is that "any" (the most
> general object - the union of all possible types) is at the top of the
> type hierarchy, and therefore "none" (the void set - the intersection of
> all possible types) is at the bottom.

As I see it, your reasoning is true, when applied to dynamically typed
language:

struct any {};
struct derived : any { void f(); }

any a = derived();
a.f(); // ok, f() is found dynamically

but it could not be applied to statically typed C++:

struct any { virtual ~any() {} };
struct derived : any { void f(); }

any a = derived();
a.f(); // error, f() is not a member of "any"

derived& d = dynamic_cast<derived&>(a)
d.f(); // ok, f() is a member of "derived"

Therefore, C++ have "void" (intersection of types) at the top of the
hierarchy, and "any" (union of types) at the bottom, while the
dynamically typed language have them in reverse order.


> The rationale is this: a function that never return could statically
> claim it returns any type, because a return will never substantiate. As
> many people have noticed, this particularity confers "none" remarkable
> expressiveness.

Again, we will notice that "return will never substantiate" at runtime,
so it fits well into dynamically typed phylosophy only.


> My implementation goes like:
>
> struct None {
> None() { throw "can't happen"; }
> None(const None&) {}
> template <class T> operator T&() const {
> throw "can't happen";
> return *this;
> }
> };

Which is a good example of implementing it in a dynamically typed way.


> As an aside, I disagree with the idea of making "none" an exception for
> operators && and ||. Instead, a "none" type that converts to anything
> could work in conjunction with operators || and && if the language
> preserved the type of the operators: operator|| would return its first
> argument (whatever its type is) if it evaluates to nonzero, and its
> second argument otherwise. Similarly, operator&& would return its first
> argument if it does NOT evaluate to nonzero, and its second argument
> otherwise.
>
> But, while sensible, these all are changes that are too far-reaching and
> with too little benefits to be considered seriously.

It reaches too far to Perl, because Perl has exactly that semantics for
its || and &&. By the way, I like Perl, but this behavior is absolutely
incompatible with statically typed nature of C++.


--
Andrei Polushin

Andrei Alexandrescu (See Website For Email)

unread,
Oct 24, 2006, 3:35:33 PM10/24/06
to
Andrei Polushin wrote:
> Andrei Alexandrescu wrote:
>> The idea is more general; I stole it from the Cecil language, which has
>> a "none" type at the bottom of the type hierarchy, meaning that it is a
>> subtype of any other type. One nice intuition is that "any" (the most
>> general object - the union of all possible types) is at the top of the
>> type hierarchy, and therefore "none" (the void set - the intersection of
>> all possible types) is at the bottom.
>
> As I see it, your reasoning is true, when applied to dynamically typed
> language:
>
> struct any {};
> struct derived : any { void f(); }
>
> any a = derived();
> a.f(); // ok, f() is found dynamically
>
> but it could not be applied to statically typed C++:
>
> struct any { virtual ~any() {} };
> struct derived : any { void f(); }
>
> any a = derived();
> a.f(); // error, f() is not a member of "any"
>
> derived& d = dynamic_cast<derived&>(a)
> d.f(); // ok, f() is a member of "derived"
>
> Therefore, C++ have "void" (intersection of types) at the top of the
> hierarchy, and "any" (union of types) at the bottom, while the
> dynamically typed language have them in reverse order.

I am sorry, I am unable to make sense on where dynamism comes into play
into our discussion of functions that don't return. I can't also
understand what the examples are emphasizing. The best I can do given
the circumstances is trying to re-explain myself in different words.

In any statically-typed language with objects linked by inheritance
relationships (a directed acyclic graph (DAG) with one or more roots),
we could think of two particular nodes: the "top" of the graph (an
existing or imaginary node to which all of the roots of the graph
point), and the "bottom" (an existing or imaginary node that points to
all of the leaves in the graph).

By "points to" above I mean "has a directed arrow from itself to".

The top and the bottom nodes in the DAG correspond to two types with
certain properties. The language may or may not define them. Let's take
a look at the properties.

Top: is the most general type that comprehends all other types. In
inclusion polymorphism (= usual, C++/Java/C# etc.), top can be thought
of as the union of all static types. In Java "Object" is that type. C++
does not prescribe a top type. Claiming that void is that type of C++
would be quite a stretch as void is a type that cannot have a value. It
might be safer to say that void* is the top of all pointer types.

Bottom: is the most particular type, one that inherits every other type
in the typed universe. Because multiple inheritance is intersection of
types, bottom is the intersection of all types. If the typed universe is
considered potentially infinite (a reasonable assumption), bottom cannot
have an instance. Due to its position in the graph, bottom can
statically be converted to any type.

>> My implementation goes like:
>>
>> struct None {
>> None() { throw "can't happen"; }
>> None(const None&) {}
>> template <class T> operator T&() const {
>> throw "can't happen";
>> return *this;
>> }
>> };
>
> Which is a good example of implementing it in a dynamically typed way.

I don't understand where dynamism intervenes.


Andrei

Andrei Polushin

unread,
Oct 24, 2006, 8:16:22 PM10/24/06
to
Andrei Alexandrescu wrote:
>>> The idea is more general; I stole it from the Cecil language, which has
>>> a "none" type at the bottom of the type hierarchy, meaning that it is a
>>> subtype of any other type. One nice intuition is that "any" (the most
>>> general object - the union of all possible types) is at the top of the
>>> type hierarchy, and therefore "none" (the void set - the intersection of
>>> all possible types) is at the bottom.

Well, sorry, I've misunderstand the "void set" as "void type".

> Top: is the most general type that comprehends all other types. In
> inclusion polymorphism (= usual, C++/Java/C# etc.), top can be thought
> of as the union of all static types. In Java "Object" is that type. C++
> does not prescribe a top type. Claiming that void is that type of C++
> would be quite a stretch as void is a type that cannot have a value. It
> might be safer to say that void* is the top of all pointer types.

Now I clearly see that you mean that "type void" == "type any" is
at the top.

> Bottom: is the most particular type, one that inherits every other type
> in the typed universe. Because multiple inheritance is intersection of
> types, bottom is the intersection of all types. If the typed universe is
> considered potentially infinite (a reasonable assumption), bottom cannot
> have an instance. Due to its position in the graph, bottom can
> statically be converted to any type.

And the type "none" is at the bottom. OK. Trying to live with this.

Simple question: having the imaginary variable of type "none", can I
access its methods? As far as I know at this time, the type "none"
derives from Object, File, std::string, float and everything else,
so I write:

void f(const none& x) {
x.File::read();
}

Probably, I will be never allowed to instantiate the object of type
"none", but when do I know about that? Only at runtime?

So this code compiles?

--
Andrei Polushin

Andrei Alexandrescu (See Website For Email)

unread,
Oct 24, 2006, 9:06:34 PM10/24/06
to
Andrei Polushin wrote:
> Simple question: having the imaginary variable of type "none", can I
> access its methods? As far as I know at this time, the type "none"
> derives from Object, File, std::string, float and everything else,
> so I write:
>
> void f(const none& x) {
> x.File::read();
> }
>
> Probably, I will be never allowed to instantiate the object of type
> "none", but when do I know about that? Only at runtime?
>
> So this code compiles?

That's a good question. It depends how the language wants to define
none. The important element to remember is that code using function
calls, member variables access, etc. is always statically correct simply
because it's unreachable.

So the code above, depending on how a hypothetical extended C++ might
define none, may:

a) Compile without an error;

b) Not compile because type none has no accessible members;

c) Not compile because you're not allowed to name a variable of type none.

Again, the important tidbit is - they are all statically correct.


Andrei

Andrei Polushin

unread,
Oct 24, 2006, 11:41:54 PM10/24/06
to
Andrei Alexandrescu wrote:

> Andrei Polushin wrote:
>> so I write:
>>
>> void f(const none& x) {
>> x.File::read();
>> }
>>
>> Probably, I will be never allowed to instantiate the object of type
>> "none", but when do I know about that? Only at runtime?
>>
>> So this code compiles?
>
> That's a good question. It depends how the language wants to define
> none. The important element to remember is that code using function
> calls, member variables access, etc. is always statically correct simply
> because it's unreachable.
>
> So the code above, depending on how a hypothetical extended C++ might
> define none, may:
>
> a) Compile without an error;
>
> b) Not compile because type none has no accessible members;
>
> c) Not compile because you're not allowed to name a variable of type none.
>
> Again, the important tidbit is - they are all statically correct.

I don't like it to be statically correct.

Recall your type:

> struct None {
> None() { throw "can't happen"; }
> None(const None&) {}
> template <class T> operator T&() const {
> throw "can't happen";
> return *this;
> }
> };

With a small adaptation for truly dynamic language it looks like:

struct None : extends /all-the-types/ {
None() {
throw "can't create";


}
None(const None&) {
}
template <class T> operator T&() const {

throw "can't convert";
return *this;
}
template <class R, class T...> R invoke(string name, T...) {
throw "can't invoke method with the specified name";
}
};

Now it can't even invoke the method from the base class because of
runtime error.

It should differ from the dynamic language in the following way: the
thing that generates a runtime error in dynamically typed language
should generate a compile-time error in statically typed language.

For the statically typed language we may have the following:

struct None : extends /all-the-types/ {
None() {
static_assert(false, "can't create");


}
None(const None&) {
}
template <class T> operator T&() const {

static_assert(false, "can't convert");
return *this;
}
template <class R, class T...> R invoke(string name, T...) {
static_assert(false, "can't invoke method");
}
};

That reduces to the following equivalent C++ syntax:

struct None {
private:
None() {}
public:
None(const None&) {}
};

I think we should define std::noreturn_t this way.


--
Andrei Polushin

Seungbeom Kim

unread,
Oct 25, 2006, 1:17:18 PM10/25/06
to
Andrei Polushin wrote:
> Andrei Alexandrescu wrote:
>> As an aside, I disagree with the idea of making "none" an exception for
>> operators && and ||. Instead, a "none" type that converts to anything
>> could work in conjunction with operators || and && if the language
>> preserved the type of the operators: operator|| would return its first
>> argument (whatever its type is) if it evaluates to nonzero, and its
>> second argument otherwise. Similarly, operator&& would return its first
>> argument if it does NOT evaluate to nonzero, and its second argument
>> otherwise.
>>
>> But, while sensible, these all are changes that are too far-reaching and
>> with too little benefits to be considered seriously.
>
> It reaches too far to Perl, because Perl has exactly that semantics for
> its || and &&. By the way, I like Perl, but this behavior is absolutely
> incompatible with statically typed nature of C++.

How is the proposed semantics incompatible with the statically-typed nature
of C++, can you elaborate? I don't see anything dynamic or polymorphic here.

--
Seungbeom Kim

Andrei Polushin

unread,
Oct 25, 2006, 6:48:13 PM10/25/06
to
Seungbeom Kim wrote:
> Andrei Polushin wrote:
>> Andrei Alexandrescu wrote:
>>> As an aside, I disagree with the idea of making "none" an exception for
>>> operators && and ||. Instead, a "none" type that converts to anything
>>> could work in conjunction with operators || and && if the language
>>> preserved the type of the operators: operator|| would return its first
>>> argument (whatever its type is) if it evaluates to nonzero, and its
>>> second argument otherwise. Similarly, operator&& would return its first
>>> argument if it does NOT evaluate to nonzero, and its second argument
>>> otherwise.
>>>
>>> But, while sensible, these all are changes that are too far-reaching and
>>> with too little benefits to be considered seriously.
>> It reaches too far to Perl, because Perl has exactly that semantics for
>> its || and &&. By the way, I like Perl, but this behavior is absolutely
>> incompatible with statically typed nature of C++.
>
> How is the proposed semantics incompatible with the statically-typed nature
> of C++, can you elaborate? I don't see anything dynamic or polymorphic here.

I've just said that pulling the behavior of Perl (dynamically typed
language) into C++ (statically typed language) may not be appropriate.

In C++, the operands of || are converted to bool, that's why the result
of operator || is bool. In contrast, Perl preserves the types of the
operands, because it is able to evaluate them without conversion.

So I'm not arguing against the proposed semantics of || itself, but I
think we already have exactly that semantics, translated into the
philosophy of statically typed language.


--
Andrei Polushin

Andrei Alexandrescu (See Website For Email)

unread,
Oct 26, 2006, 1:45:16 PM10/26/06
to
Andrei Polushin wrote:
> I've just said that pulling the behavior of Perl (dynamically typed
> language) into C++ (statically typed language) may not be appropriate.
>
> In C++, the operands of || are converted to bool, that's why the result
> of operator || is bool. In contrast, Perl preserves the types of the
> operands, because it is able to evaluate them without conversion.

The fact that this discussion is futile for the purpose of
standardization does not need to make it any less rigorous.

This is a misunderstanding caused by me; I apologize. What I meant was
that the type would be preserved, I had in my mind that the types of the
two operands must still be identical. For example, you could not do this:

void Fun(int * a, bool b) {
a || b;
}

because the type of the or-expression would be undetermined. But this is
sound:

void Fun(int * a, bool b) {
a || new int[1];
b || (3 == 5);
}

So the semantics of operator|| and operator&& could extend to any type
comparable against the literal 0 to return one of the two operands, as
long as the two operands have the same static type. The type of the
entire expression would then bear that static type.

This would be useful in a number of situations:

const int port = GetOption<int>("port") || 22;
int * p = local_pool.alloc(size) || global_pool.alloc(size);

and so on.

> So I'm not arguing against the proposed semantics of || itself, but I
> think we already have exactly that semantics, translated into the
> philosophy of statically typed language.

The semantics are not exactly there, because they insist on the result
of operator|| and && to be Boolean.

Sorry about the distraction.


Andrei

Andrei Polushin

unread,
Oct 26, 2006, 4:03:22 PM10/26/06
to
Andrei Alexandrescu wrote:
> So the semantics of operator|| and operator&& could extend to any type
> comparable against the literal 0 to return one of the two operands, as
> long as the two operands have the same static type. The type of the
> entire expression would then bear that static type.

Interestingly, there is no typesafe way to compare custom type against
literal 0 in current C++:

struct A {
bool operator==(int null) { // comparable against literal 0
assert(null == 0); // not typesafe, so runtime check
return is_true();
}
private:
bool is_true() const;
};

That's because the type of literal 0 is at the bottom of the hierarchy
of built-in types (in your terminology). This type is not typesafe.
Strictly speaking, it is the concession made to dynamically typed
philosophy.

Probably, you have to redefine the semantics of || in another way.

Or let it be, I don't know.


--
Andrei Polushin

Andrei Alexandrescu (See Website For Email)

unread,
Oct 26, 2006, 4:12:06 PM10/26/06
to
Andrei Polushin wrote:
> Andrei Alexandrescu wrote:
>> So the semantics of operator|| and operator&& could extend to any type
>> comparable against the literal 0 to return one of the two operands, as
>> long as the two operands have the same static type. The type of the
>> entire expression would then bear that static type.
>
> Interestingly, there is no typesafe way to compare custom type against
> literal 0 in current C++:
>
> struct A {
> bool operator==(int null) { // comparable against literal 0
> assert(null == 0); // not typesafe, so runtime check
> return is_true();
> }
> private:
> bool is_true() const;
> };
>
> That's because the type of literal 0 is at the bottom of the hierarchy
> of built-in types (in your terminology). This type is not typesafe.
> Strictly speaking, it is the concession made to dynamically typed
> philosophy.
>
> Probably, you have to redefine the semantics of || in another way.

This is a non-issue as the entire discussion was about the built-in
operator|| and operator&&. Their overloaded versions can return whatever
type they please and as such the discussion does not apply to them.

The type of 0 is not at the bottom of C++'s type hierarchy. It's an
octal integral constant, to which the language confers the extra ability
to compare against pointers.


Andrei

Andrei Polushin

unread,
Oct 26, 2006, 6:01:30 PM10/26/06
to
Andrei Alexandrescu wrote:
> Andrei Polushin wrote:
>> That's because the type of literal 0 is at the bottom of the hierarchy
>> of built-in types (in your terminology). This type is not typesafe.
>> Strictly speaking, it is the concession made to dynamically typed
>> philosophy.
> [...]

>
> The type of 0 is not at the bottom of C++'s type hierarchy.

of the *built-in* types' hierarchy.

> It's an
> octal integral constant, to which the language confers the extra ability
> to compare against pointers.

This conclusion looks like a repetition of the language rules, it
induces nothing about why there is such an exception in those rules.

By the way, there is nullptr proposal,
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1601.pdf
that also fails to define the type to which the "nullptr" points.

As long as nullptr should be converted to any other pointer, it is

namespace std {
struct noreturn_t; // incomplete type, derived from all types

noreturn_t*const nullptr = 0;
}

Here we have rules without any exceptions.


--
Andrei Polushin

Tommi Höynälänmaa

unread,
Oct 28, 2006, 9:06:49 PM10/28/06
to
Andrei Polushin kirjoitti:

> I propose to add a special type, std::noreturn_t, to indicate that
> function does never return.
>
> Actually, some compilers have the similar feature, known as either
> __declspec(noreturn) or __attribute__ ((noreturn)), but it is used
> primarily for optimization purposes and does not affect language.

This is a question about the execution flow and not about the result
type of a function. There would be no values whose type is noreturn_t.
So "noreturn" should be an attribute of a function like the throw
specifications.

"noreturn" and other attributes should be part of the type of the
function (i.e. function pointer type), not the type of the result value.

BTW, there should also be attribute "pure" to indicate that a function
has no side effects. A pure function would not be allowed to call any
non-pure functions or to change global variables. GCC implements "pure"
and "const" attributes for pure functions.

C++ should also have a standardized syntax for the (standard) function
attributes. Perhaps something like

void myfunc(int a, char b) attributes(noreturn)

A function with the pure attribute would be compatible with the
corresponding function type with or without the pure attribute. A
function without the pure attribute would not be compatible with
the corresponding function type with the pure attribute.

There could be another attribute "mustreturn" to indicate that a
function has to return. "mustreturn" and "noreturn" would be mutually
exclusive.
Then
- A function with neither of the return attributes would be compatible
with the corresponding function type with or without "noreturn" or
"mustreturn" attributes.
- A function with "noreturn" attribute would not be compatible with the
corresponding function type with the "mustreturn" attribute.
- A function with "mustreturn" attribute would not be compatible with
the corresponding function type with the "noreturn" attribute.

--
Tommi Höynälänmaa
sähköposti / e-mail: tommi.ho...@iki.fi
kotisivu / homepage: http://www.iki.fi/tohoyn/

Andrei Alexandrescu (See Website For Email)

unread,
Oct 29, 2006, 12:22:50 AM10/29/06
to
Tommi Höynälänmaa wrote:
> Andrei Polushin kirjoitti:
>> I propose to add a special type, std::noreturn_t, to indicate that
>> function does never return.
>>
>> Actually, some compilers have the similar feature, known as either
>> __declspec(noreturn) or __attribute__ ((noreturn)), but it is used
>> primarily for optimization purposes and does not affect language.
>
> This is a question about the execution flow and not about the result
> type of a function. There would be no values whose type is noreturn_t.
> So "noreturn" should be an attribute of a function like the throw
> specifications.

The point of the idea was different. Yes, values of that type cannot
exist, but the properties of the static type control the expressiveness
of the idioms in which the type could take part. And precisely because
values of that "none" type can never exist, there is considerable static
freedom with regard to the static properties of that type, which confers
it remarkable expressiveness.

> "noreturn" and other attributes should be part of the type of the
> function (i.e. function pointer type), not the type of the result value.
>
> BTW, there should also be attribute "pure" to indicate that a function
> has no side effects. A pure function would not be allowed to call any
> non-pure functions or to change global variables. GCC implements "pure"
> and "const" attributes for pure functions.

Pure is a good comparison point with no-return as it is purely (no pun
intended) related to a flow property of the function, with (to the best
of my imagination) is not related to its type.

> A function with the pure attribute would be compatible with the
> corresponding function type with or without the pure attribute. A
> function without the pure attribute would not be compatible with
> the corresponding function type with the pure attribute.
>
> There could be another attribute "mustreturn" to indicate that a
> function has to return. "mustreturn" and "noreturn" would be mutually
> exclusive.

I guess mustreturn is simply a static incarnation of the nothrow idea.
Things get tricky when we start thinking of mustreturn as a guarantee
that the function doesn't enter infinite loops - it wouldn't generally
be quite possible to demonstrate that a function violates that
assumption :o).


Andrei

Tommi Höynälänmaa

unread,
Nov 1, 2006, 5:31:51 PM11/1/06
to
Tommi Höynälänmaa kirjoitti:

> There could be another attribute "mustreturn" to indicate that a
> function has to return. "mustreturn" and "noreturn" would be mutually
> exclusive.
> Then
> - A function with neither of the return attributes would be compatible
> with the corresponding function type with or without "noreturn" or
> "mustreturn" attributes.

It would be semantically more reasonable to change this rule so that a
function with neither of the return attributes would not be compatible
with the corresponding function type with the "noreturn" attribute. If
we declare a function pointer with a type that has the "noreturn"
attribute then the values of this pointer should be guaranteed not to
return.

Probably the rule should be changed for "mustreturn" in the same way.
The semantics of "mustreturn" are more unclear, though.

Tommi Höynälänmaa

unread,
Nov 4, 2006, 7:51:33 PM11/4/06
to
Andrei Alexandrescu (See Website For Email) kirjoitti:

> Tommi Höynälänmaa wrote:
>> This is a question about the execution flow and not about the result
>> type of a function. There would be no values whose type is noreturn_t.
>> So "noreturn" should be an attribute of a function like the throw
>> specifications.
>
> The point of the idea was different. Yes, values of that type cannot
> exist, but the properties of the static type control the expressiveness
> of the idioms in which the type could take part. And precisely because
> values of that "none" type can never exist, there is considerable static
> freedom with regard to the static properties of that type, which confers
> it remarkable expressiveness.

But isn't "none" roughly equivalent to the void result type in C++?
Function that returns nothing has also a result type that contains no
values. Or does it have some other result type? I think this type should
also be at the bottom of the type hierarchy.

>> There could be another attribute "mustreturn" to indicate that a
>> function has to return. "mustreturn" and "noreturn" would be mutually
>> exclusive.
>
> I guess mustreturn is simply a static incarnation of the nothrow idea.
> Things get tricky when we start thinking of mustreturn as a guarantee
> that the function doesn't enter infinite loops - it wouldn't generally
> be quite possible to demonstrate that a function violates that
> assumption :o).

We could define "mustreturn" e.g. so that
1) A "mustreturn" function may call only other "mustreturn" functions
or
2) A "mustreturn" function may not call any "noreturn" functions.
Infinite loops and things like that would not be considered at all.
The semantics of "mustreturn" would be a bit unclear this way because it
would be possible that a "mustreturn" function executes an infinite time.

However, it may be that "mustreturn" is not worth implementing.

--
Tommi Höynälänmaa
sähköposti / e-mail: tommi.ho...@iki.fi
kotisivu / homepage: http://www.iki.fi/tohoyn/

---

Andrei Alexandrescu (See Website For Email)

unread,
Nov 4, 2006, 10:14:26 PM11/4/06
to
Tommi Höynälänmaa wrote:
> Andrei Alexandrescu (See Website For Email) kirjoitti:
>> Tommi Höynälänmaa wrote:
>>> This is a question about the execution flow and not about the result
>>> type of a function. There would be no values whose type is noreturn_t.
>>> So "noreturn" should be an attribute of a function like the throw
>>> specifications.
>>
>> The point of the idea was different. Yes, values of that type cannot
>> exist, but the properties of the static type control the
>> expressiveness of the idioms in which the type could take part. And
>> precisely because values of that "none" type can never exist, there is
>> considerable static freedom with regard to the static properties of
>> that type, which confers it remarkable expressiveness.
>
> But isn't "none" roughly equivalent to the void result type in C++?
> Function that returns nothing has also a result type that contains no
> values. Or does it have some other result type? I think this type should
> also be at the bottom of the type hierarchy.

There are similarities between void and none, but also obvious
differences. I think we've beaten this horse long enough :o).

>>> There could be another attribute "mustreturn" to indicate that a
>>> function has to return. "mustreturn" and "noreturn" would be mutually
>>> exclusive.
>>
>> I guess mustreturn is simply a static incarnation of the nothrow idea.
>> Things get tricky when we start thinking of mustreturn as a guarantee
>> that the function doesn't enter infinite loops - it wouldn't generally
>> be quite possible to demonstrate that a function violates that
>> assumption :o).
>
> We could define "mustreturn" e.g. so that
> 1) A "mustreturn" function may call only other "mustreturn" functions
> or
> 2) A "mustreturn" function may not call any "noreturn" functions.
> Infinite loops and things like that would not be considered at all.
> The semantics of "mustreturn" would be a bit unclear this way because it
> would be possible that a "mustreturn" function executes an infinite time.
>
> However, it may be that "mustreturn" is not worth implementing.

I also don't think "mustreturn" has much value. For a host of
theoretical and practical reasons, "nothrow" is much more worthy
focusing on.


Andrei

Andrei Polushin

unread,
Nov 4, 2006, 11:42:26 PM11/4/06
to
Tommi Höynälänmaa wrote:
> Andrei Alexandrescu (See Website For Email) kirjoitti:
>> Tommi Höynälänmaa wrote:
>>> This is a question about the execution flow and not about the result
>>> type of a function. There would be no values whose type is noreturn_t.
>>> So "noreturn" should be an attribute of a function like the throw
>>> specifications.
>>
>> The point of the idea was different. Yes, values of that type cannot
>> exist, but the properties of the static type control the
>> expressiveness of the idioms in which the type could take part. And
>> precisely because values of that "none" type can never exist, there is
>> considerable static freedom with regard to the static properties of
>> that type, which confers it remarkable expressiveness.
>
> But isn't "none" roughly equivalent to the void result type in C++?
> Function that returns nothing has also a result type that contains no
> values. Or does it have some other result type? I think this type should
> also be at the bottom of the type hierarchy.

It depends on your view. From my point of view, void is the type that
can have an empty value. I'm allowed to upcast to void and to void*:

void f() {
return (void)1; // explicit upcast to void
}

void* p = (char*)0; // implicit upcast to pointer-to-void

"upcast to void" suggests that void is at the top. The type at the
bottom upcasts to any other type, and we also have some suggestsions:

none* NULL = 0; // pointer convertible to any pointer
none* malloc(); // returns pointer convertible to any pointer, in C

char* p = malloc(22); // illegal in C++, but legal in C
if (p != NULL) { // implicit upcast: (char*)NULL
// ...
}

Both none and void cannot be instantiated, but for different reasons:

- void is an empty set
- none is the universe set

Now the function can return either

- something (of valid type), or
- nothing (of void type), which is less-than-something, or
- everything (of universe type), which is greater-than-anything.

We may say that if it returns everything, it should execute every
statement that will instantiate every part of that everything, which
will take an infinite time. We may either relax and wait, or claim
that it will never return. If we assert that it will never return,
we are allowed to exploit that assertion for the life of the program.


>>> There could be another attribute "mustreturn" to indicate that a
>>> function has to return. "mustreturn" and "noreturn" would be mutually
>>> exclusive.
>>
>> I guess mustreturn is simply a static incarnation of the nothrow idea.
>> Things get tricky when we start thinking of mustreturn as a guarantee
>> that the function doesn't enter infinite loops - it wouldn't generally
>> be quite possible to demonstrate that a function violates that
>> assumption :o).
>
> We could define "mustreturn" e.g. so that
> 1) A "mustreturn" function may call only other "mustreturn" functions
> or
> 2) A "mustreturn" function may not call any "noreturn" functions.
> Infinite loops and things like that would not be considered at all.
> The semantics of "mustreturn" would be a bit unclear this way because it
> would be possible that a "mustreturn" function executes an infinite time.
>
> However, it may be that "mustreturn" is not worth implementing.

I guess it might be worth or not, depending on its exact definition.

If you propose it for static control flow analysis, it could be
slightly useful instead of throw() specification, e.g. other languages
(C, Pascal, Fortran) may accept callback functions that must return
and should not throw. The same is about OS callbacks, COM methods etc.
But that may involve too many changes in the C library functions.

Anyway, "mustreturn" is a standalone proposal, not necessary related
to "noreturn_t".

--
Andrei Polushin

0 new messages