Topic: noreturn_t
Author: "Andrei Polushin" <polushin@gmail.com>
Date: Sat, 4 Nov 2006 22:42:26 CST Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Andrei Polushin" <polushin@gmail.com>
Date: Wed, 25 Oct 2006 16:48:13 CST Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail@erdani.org>
Date: Thu, 26 Oct 2006 11:45:16 CST Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Andrei Polushin" <polushin@gmail.com>
Date: Thu, 26 Oct 2006 14:03:22 CST Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: SeeWebsiteForEmail@erdani.org ("Andrei Alexandrescu (See Website For Email)")
Date: Thu, 26 Oct 2006 20:12:06 GMT Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Andrei Polushin" <polushin@gmail.com>
Date: Thu, 26 Oct 2006 16:01:30 CST Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Andrei Polushin" <polushin@gmail.com>
Date: Mon, 23 Oct 2006 10:55:58 CST Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Andrei Polushin" <polushin@gmail.com>
Date: Mon, 23 Oct 2006 10:57:24 CST Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Andrei Polushin" <polushin@gmail.com>
Date: Mon, 23 Oct 2006 10:55:41 CST Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Andrei Polushin" <polushin@gmail.com>
Date: Mon, 23 Oct 2006 11:18:45 CST Raw View
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]
--
Andrei Polushin
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Alberto Ganesh Barbati <AlbertoBarbati@libero.it>
Date: Mon, 23 Oct 2006 13:17:41 CST Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: SeeWebsiteForEmail@erdani.org ("Andrei Alexandrescu (See Website For Email)")
Date: Tue, 24 Oct 2006 00:01:54 GMT Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Andrei Polushin" <polushin@gmail.com>
Date: Tue, 24 Oct 2006 11:24:46 CST Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail@erdani.org>
Date: Tue, 24 Oct 2006 13:35:33 CST Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Andrei Polushin" <polushin@gmail.com>
Date: Tue, 24 Oct 2006 18:16:22 CST Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail@erdani.org>
Date: Tue, 24 Oct 2006 19:06:34 CST Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Andrei Polushin" <polushin@gmail.com>
Date: Tue, 24 Oct 2006 21:41:54 CST Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: musiphil@bawi.org (Seungbeom Kim)
Date: Wed, 25 Oct 2006 17:17:18 GMT Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]