P0146R0 Regular Void

205 views
Skip to first unread message

Matt Calabrese

unread,
Oct 5, 2015, 2:43:06 PM10/5/15
to ISO C++ Standard - Future Proposals
Following the "Allow values of void" discussion, I've put together a proposal[1] for updating void to be a Regular type. I'm starting a new thread for this paper specifically to get feedback on this proposal, which will be presented at Kona. If there are questions or criticisms of the idea, try to keep them in the context of this paper. In particular, I'd like to keep this discussion a bit more grounded than the other and more specific to precisely what is proposed here -- what might this proposal break (apart from what is already explicitly covered in the paper unless there is more to add), are there examples of problems that people expect to be solved by void that are not addressed here, do people see problems with any of the more subtle issues regarding the details of the proposed changes to the standard itself, etc.. I'm not strictly against tangents about void remaining a non-instantiable type, but if you are of that camp, please present your argument in the context of this paper and with real world examples, for instance how such an approach would apply to the logging examples presented in the paper, what problems, if any, such an approach might solve that this cannot, etc..

[1] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0146r0.html

Tony V E

unread,
Oct 5, 2015, 3:37:08 PM10/5/15
to Standard Proposals
On Mon, Oct 5, 2015 at 2:43 PM, Matt Calabrese <cala...@google.com> wrote:
Following the "Allow values of void" discussion, I've put together a proposal[1] for updating void to be a Regular type. I'm starting a new thread for this paper specifically to get feedback on this proposal, which will be presented at Kona. If there are questions or criticisms of the idea, try to keep them in the context of this paper. In particular, I'd like to keep this discussion a bit more grounded than the other and more specific to precisely what is proposed here -- what might this proposal break (apart from what is already explicitly covered in the paper unless there is more to add), are there examples of problems that people expect to be solved by void that are not addressed here, do people see problems with any of the more subtle issues regarding the details of the proposed changes to the standard itself, etc.. I'm not strictly against tangents about void remaining a non-instantiable type, but if you are of that camp, please present your argument in the context of this paper and with real world examples, for instance how such an approach would apply to the logging examples presented in the paper, what problems, if any, such an approach might solve that this cannot, etc..

[1] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0146r0.html

--

"For instance, if you were to make an array of such a void type, a pointer, at least in the traditional sense, would no longer be able to be used as an iterator into that array (notably meaning that generic code which relies on this would now fail for such a size 0 type)."

You could still iterate.  Since ptr++ would add 0 to the pointer, you would still always point to a/the void.
What you can't do is take the distance.  Or reach the end.

Some Ranges have similar problems.

Anyhow, I think the main purpose of the proposal will be to just see how the committee reacts to the idea.  I think you've done a good job at "it's not as big/scary as it sounds", but the reaction might still be "too scary", regardless of the details.  I almost don't think there's much sense discussing it here until we get a feel from the committee as to whether ANY regularized-void idea has a chance.

Tony


Vadim Petrochenkov

unread,
Oct 5, 2015, 4:13:18 PM10/5/15
to ISO C++ Standard - Future Proposals
>You could still iterate.  Since ptr++ would add 0 to the pointer, you would still always point to a/the void.

First class zero sized types are totally possible, they just require nontrivial amount of work.
As an example, Rust have them and uses them ubiquitously. They are quite handy - no need for empty base optimization, compressed pairs, void is a regular zero sized type indeed, etc. but they have really be accounted for in various places including collections and algorithms, moreover C++ has ABI stability considerations. So, they have to be a separate proposal and keeping this void proposal focused is a good idea.

Matt Calabrese

unread,
Oct 5, 2015, 4:34:43 PM10/5/15
to ISO C++ Standard - Future Proposals
On Mon, Oct 5, 2015 at 12:37 PM, Tony V E <tvan...@gmail.com> wrote:
On Mon, Oct 5, 2015 at 2:43 PM, Matt Calabrese <cala...@google.com> wrote:
Following the "Allow values of void" discussion, I've put together a proposal[1] for updating void to be a Regular type. I'm starting a new thread for this paper specifically to get feedback on this proposal, which will be presented at Kona. If there are questions or criticisms of the idea, try to keep them in the context of this paper. In particular, I'd like to keep this discussion a bit more grounded than the other and more specific to precisely what is proposed here -- what might this proposal break (apart from what is already explicitly covered in the paper unless there is more to add), are there examples of problems that people expect to be solved by void that are not addressed here, do people see problems with any of the more subtle issues regarding the details of the proposed changes to the standard itself, etc.. I'm not strictly against tangents about void remaining a non-instantiable type, but if you are of that camp, please present your argument in the context of this paper and with real world examples, for instance how such an approach would apply to the logging examples presented in the paper, what problems, if any, such an approach might solve that this cannot, etc..

[1] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0146r0.html

--

"For instance, if you were to make an array of such a void type, a pointer, at least in the traditional sense, would no longer be able to be used as an iterator into that array (notably meaning that generic code which relies on this would now fail for such a size 0 type)."

You could still iterate.  Since ptr++ would add 0 to the pointer, you would still always point to a/the void.
What you can't do is take the distance.  Or reach the end.

Right, but in other places where you have infinite ranges, whether or not the range is infinite does not usually simply depend on the element type. That makes things really difficult. The main concern is code like the following:

//////////
template<class T>
void foo()
{
  T bar[10];
  for(T* curr = bar, end = curr + 10; curr != end; ++curr) {}
}
//////////

For most Ts (all T's that can appear as an array element, currently) this would iterate over each element. For void, if you make it size 0, this would be an infinite loop. In places where void comes up as a dependent type, you shouldn't be expected to special-case. It should just work. In this case you could use range-based-for and the standard could have std::begin/std::end yield something other than a pointer, but that is only a partial solution.

My personal thoughts are that, at a higher level (outside of the scope of this paper) we shouldn't assume that pointers are valid array iterators, which would break down for size 0 types. I'm not about to make this proposal try to tackle this and other issues since these problems are somewhat orthogonal. Sean really wants size 0 void (and size 0 other types), and I do as well, I just don't think it's feasible at this point in time as a part of this proposal. It can still happen, but solving that problem is much more difficult and there are many more subtle issues that come from size 0 types regarding assumptions people are allowed to make in current code (that type + address is unique for each object, for example). Leaving void just unspecified on size for now (but still >= 1 as other types) is fine, I think, and if we ever get size 0 types, I'd expect implementations to make void be one of those types.


On Mon, Oct 5, 2015 at 12:37 PM, Tony V E <tvan...@gmail.com> wrote:
Anyhow, I think the main purpose of the proposal will be to just see how the committee reacts to the idea.  I think you've done a good job at "it's not as big/scary as it sounds", but the reaction might still be "too scary", regardless of the details.  I almost don't think there's much sense discussing it here until we get a feel from the committee as to whether ANY regularized-void idea has a chance.

Yeah, that's my hope, though I'd like to have a better understanding of reasons for push back prior to the meeting. In the other thread and elsewhere, it seems as though some people have more philosophical concerns, and I'd like to see these concerns explored in the context of this proposal, especially regarding the two logging code snippets. Further, the actual textual changes to the standard can be examined for any unintended consequences.

Miro Knejp

unread,
Oct 5, 2015, 4:38:35 PM10/5/15
to std-pr...@isocpp.org
You specifically mention possibly re-introducing the int "foo(void)" syntax but note that it may create problems because it isn't an unary function. Now I think this can be solved with three adjustments, let me see if I missed anything:

(1) Let any function declared with no formal parameters "int foo()" be a shortcut for and implicitly identical to "int foo(void)".
(2) Let any function that only takes a single formal parameter of type void be callable with an empty argument list.
(3) When matching template arguments an explicit overload/specialization of "int()" binds stronger than "int(T)" (where T <- void)

Rule (3) is only there to not change behavior of existing code that is already specialized for nullary functions. I think this would make "int()" and "int(void)" interchangable in every context and (conceptually) remove all nullary functions from the language which IMHO is a Good Thing.

Does this make sense?


Am 05.10.2015 um 20:43 schrieb Matt Calabrese:
Following the "Allow values of void" discussion, I've put together a proposal[1] for updating void to be a Regular type. I'm starting a new thread for this paper specifically to get feedback on this proposal, which will be presented at Kona. If there are questions or criticisms of the idea, try to keep them in the context of this paper. In particular, I'd like to keep this discussion a bit more grounded than the other and more specific to precisely what is proposed here -- what might this proposal break (apart from what is already explicitly covered in the paper unless there is more to add), are there examples of problems that people expect to be solved by void that are not addressed here, do people see problems with any of the more subtle issues regarding the details of the proposed changes to the standard itself, etc.. I'm not strictly against tangents about void remaining a non-instantiable type, but if you are of that camp, please present your argument in the context of this paper and with real world examples, for instance how such an approach would apply to the logging examples presented in the paper, what problems, if any, such an approach might solve that this cannot, etc..

[1] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0146r0.html
--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.

Matthew Woehlke

unread,
Oct 5, 2015, 4:53:26 PM10/5/15
to std-pr...@isocpp.org
On 2015-10-05 15:37, Tony V E wrote:
> On Mon, Oct 5, 2015 at 2:43 PM, Matt Calabrese wrote:
>> [1] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0146r0.html
>
> "For instance, if you were to make an array of such a void type, a pointer,
> at least in the traditional sense, would no longer be able to be used as an
> iterator into that array (notably meaning that generic code which relies on
> this would now fail for such a size 0 type)."
>
> You could still iterate. Since ptr++ would add 0 to the pointer, you would
> still always point to a/the void.
> What you can't do is take the distance. Or reach the end.

Or, alternatively, the distance is always 0 and you are always already
*at* the end.

That said, I still think trying to solve the problem that templates only
handle the one-value case by removing the zero case from the existing
zero-or-one that is presently allowed is the wrong solution (and wrong
direction, i.e. a regression rather than progress), and we should
instead be working towards zero-or-many support. Languages with first
class multiple return values and language level multi-value support
(e.g. unpacking) do much better in this realm.

Perhaps the largest questions I would keep in mind are:

- How do we deal with the implications of claiming that void is a
regular type, when a void function *has no return value* (keep in mind
this may have ABI and/or register allocation implications).

- If at some point we add first class multiple return values from
functions, how will we reconcile that 'void foo()' returns *zero* values
and not one value? Related, how do we explain away 'return;' as a
zero-value return in a function whose return type is specified as
"void", especially in the face of syntax to actually return multiple values?

I don't see in the changes how you reconcile claiming that void is a
regular type with the fact that a void function *doesn't return a
value*, or for that matter how 'void foo() { return; }' is still
supposed to work.

--
Matthew

Matt Calabrese

unread,
Oct 5, 2015, 4:55:23 PM10/5/15
to ISO C++ Standard - Future Proposals
On Mon, Oct 5, 2015 at 1:34 PM, Matt Calabrese <cala...@google.com> wrote:
Right, but in other places where you have infinite ranges, whether or not the range is infinite does not usually simply depend on the element type. That makes things really difficult. The main concern is code like the following:

//////////
template<class T>
void foo()
{
  T bar[10];
  for(T* curr = bar, end = curr + 10; curr != end; ++curr) {}
}
//////////

For most Ts (all T's that can appear as an array element, currently) this would iterate over each element. For void, if you make it size 0, this would be an infinite loop.

Woops, sorry, this would have the opposite problem -- it wouldn't execute any iterations of the loop. Either way, the issue is that there is a change in meaning based on the element type. The overall problem, in my opinion, again, is our direct usage of pointers as iterators for all T, instead of some more general abstraction (which could happen to be implemented exactly as an iterator when the element size != 0). I don't think it's appropriate to try to address that problem as a part of this proposal, and I fear that it may end up being too difficult of an assumption to ever change, and it may not even be worth it. The current proposal would be compatible with such a hypothetical change to the language, but it's not dependent on such a change. It's just more of a nice-to-have.

Nicol Bolas

unread,
Oct 5, 2015, 4:59:13 PM10/5/15
to ISO C++ Standard - Future Proposals
On Monday, October 5, 2015 at 4:34:43 PM UTC-4, Matt Calabrese wrote:
On Mon, Oct 5, 2015 at 12:37 PM, Tony V E <tvan...@gmail.com> wrote:
On Mon, Oct 5, 2015 at 2:43 PM, Matt Calabrese <cala...@google.com> wrote:
Following the "Allow values of void" discussion, I've put together a proposal[1] for updating void to be a Regular type. I'm starting a new thread for this paper specifically to get feedback on this proposal, which will be presented at Kona. If there are questions or criticisms of the idea, try to keep them in the context of this paper. In particular, I'd like to keep this discussion a bit more grounded than the other and more specific to precisely what is proposed here -- what might this proposal break (apart from what is already explicitly covered in the paper unless there is more to add), are there examples of problems that people expect to be solved by void that are not addressed here, do people see problems with any of the more subtle issues regarding the details of the proposed changes to the standard itself, etc.. I'm not strictly against tangents about void remaining a non-instantiable type, but if you are of that camp, please present your argument in the context of this paper and with real world examples, for instance how such an approach would apply to the logging examples presented in the paper, what problems, if any, such an approach might solve that this cannot, etc..

[1] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0146r0.html

--

"For instance, if you were to make an array of such a void type, a pointer, at least in the traditional sense, would no longer be able to be used as an iterator into that array (notably meaning that generic code which relies on this would now fail for such a size 0 type)."

You could still iterate.  Since ptr++ would add 0 to the pointer, you would still always point to a/the void.
What you can't do is take the distance.  Or reach the end.

Right, but in other places where you have infinite ranges, whether or not the range is infinite does not usually simply depend on the element type. That makes things really difficult. The main concern is code like the following:

//////////
template<class T>
void foo()
{
  T bar[10];
  for(T* curr = bar, end = curr + 10; curr != end; ++curr) {}
}
//////////

For most Ts (all T's that can appear as an array element, currently) this would iterate over each element. For void, if you make it size 0, this would be an infinite loop. In places where void comes up as a dependent type, you shouldn't be expected to special-case. It should just work. In this case you could use range-based-for and the standard could have std::begin/std::end yield something other than a pointer, but that is only a partial solution.

My personal thoughts are that, at a higher level (outside of the scope of this paper) we shouldn't assume that pointers are valid array iterators, which would break down for size 0 types.

Pointers not being valid iterators breaks pretty much the world at this point. At the very least, you'd royally honk-off SG14 if you suddenly declare that std::vector and std::array cannot use pointers for their iterators.
 
I'm not about to make this proposal try to tackle this and other issues since these problems are somewhat orthogonal. Sean really wants size 0 void (and size 0 other types), and I do as well, I just don't think it's feasible at this point in time as a part of this proposal. It can still happen, but solving that problem is much more difficult and there are many more subtle issues that come from size 0 types regarding assumptions people are allowed to make in current code (that type + address is unique for each object, for example). Leaving void just unspecified on size for now (but still >= 1 as other types) is fine, I think, and if we ever get size 0 types, I'd expect implementations to make void be one of those types.

Well, when I was working through the issues with defining stateless types (which are guaranteed to not take up space as members or base-classes of a struct), I used the general rule that the type should work like regular types as much as possible. They would have a non-zero size, for example, so you could legitimately allocate them dynamically on the heap if you had some reason to do so.

I ran into a fundamental problem with arrays through. At some point, you have to violate something. These were the options:

1) stateless members that are arrayed take up space (thus sometimes violating the "don't take up space" principle)

2) stateless members that are arrayed all have the same address (thus violating the rules of pointer arithmetic and arrays)

3) stateless members cannot be arrayed (thus violating the rule of behaving like regular types)

4) stateless types actually have sizeof return 0 (thus violating... well, all kinds of things)

I considered #3 to be the less onerous solution. We both seemed to come to the same conclusion on that one.

Miro Knejp

unread,
Oct 5, 2015, 5:24:33 PM10/5/15
to std-pr...@isocpp.org
Am 05.10.2015 um 22:53 schrieb Matthew Woehlke:
> On 2015-10-05 15:37, Tony V E wrote:
>> On Mon, Oct 5, 2015 at 2:43 PM, Matt Calabrese wrote:
>>> [1] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0146r0.html
>> "For instance, if you were to make an array of such a void type, a pointer,
>> at least in the traditional sense, would no longer be able to be used as an
>> iterator into that array (notably meaning that generic code which relies on
>> this would now fail for such a size 0 type)."
>>
>> You could still iterate. Since ptr++ would add 0 to the pointer, you would
>> still always point to a/the void.
>> What you can't do is take the distance. Or reach the end.
> Or, alternatively, the distance is always 0 and you are always already
> *at* the end.
>
> That said, I still think trying to solve the problem that templates only
> handle the one-value case by removing the zero case from the existing
> zero-or-one that is presently allowed is the wrong solution (and wrong
> direction, i.e. a regression rather than progress), and we should
> instead be working towards zero-or-many support. Languages with first
> class multiple return values and language level multi-value support
> (e.g. unpacking) do much better in this realm.
And some of these languages use the unit type to represent the 0-tuple.
>
> Perhaps the largest questions I would keep in mind are:
>
> - How do we deal with the implications of claiming that void is a
> regular type, when a void function *has no return value* (keep in mind
> this may have ABI and/or register allocation implications).
It is still a builtin type the compiler can handle under the as-if rule.
Since the void type, even if a regular unit type, has only a single
possible state it can exist in, the compiler can create one out of thin
air if required. It's existence doesn't have to be acknowledged in the
generated code or at the ABI level. It is a language-level thing only.
Yes, if you start taking a void variable's address or make it a member
variable that is where code generation may be affected, but until then
the compiler does not need to waste a single cycle or register on void
variables because they all have the same value. It still doesn't change
anything at the call/return site.
>
> - If at some point we add first class multiple return values from
> functions, how will we reconcile that 'void foo()' returns *zero* values
> and not one value? Related, how do we explain away 'return;' as a
> zero-value return in a function whose return type is specified as
> "void", especially in the face of syntax to actually return multiple values?
This is a red herring. If we look at type theory then a function
returning *no value* is a function that does not terminate by returning
to its caller (look up bottom type and unit type). The unit type is used
to identify functions that return to the caller but have no return value
that holds any meaningful information besides "I'm done". This is why
void in its current form is neither bottom nor unit type but something
inbetween.
>
> I don't see in the changes how you reconcile claiming that void is a
> regular type with the fact that a void function *doesn't return a
> value*, or for that matter how 'void foo() { return; }' is still
> supposed to work.
>
With this proposal void becomes a unit type. It can have only one
possible value. At the language level the compiler can just create it if
omitted in a return or call statement. There is no impact on code
generation or ABI breakage, it's a purely language-level abstraction
with no runtime implications unless ODR-used.

Casey Carter

unread,
Oct 5, 2015, 5:38:36 PM10/5/15
to ISO C++ Standard - Future Proposals
On Monday, October 5, 2015 at 1:43:06 PM UTC-5, Matt Calabrese wrote:

You're going to have to do something about the last sentence in stmt.return/2: "Flowing off the end of a function is equivalent to a return with no value; this results in undefined behavior in a value-returning function." The consequence of the proposed change is that all existing functions that return void will have undefined behavior.

Matt Calabrese

unread,
Oct 5, 2015, 5:39:18 PM10/5/15
to ISO C++ Standard - Future Proposals
On Mon, Oct 5, 2015 at 1:38 PM, Miro Knejp <miro....@gmail.com> wrote:
You specifically mention possibly re-introducing the int "foo(void)" syntax but note that it may create problems because it isn't an unary function. Now I think this can be solved with three adjustments, let me see if I missed anything:

I think you get it. To restate, the idea is that right now, void foo(void) is equivalent to void foo(), which we'd want to change. void foo(void) should really mean what it says, assuming that void is an object type (in other words it should be a unary function instead of a nullary function). I just don't propose that we change the meaning just yet.

 On Mon, Oct 5, 2015 at 1:38 PM, Miro Knejp <miro....@gmail.com> wrote:
(1) Let any function declared with no formal parameters "int foo()" be a shortcut for and implicitly identical to "int foo(void)".
(2) Let any function that only takes a single formal parameter of type void be callable with an empty argument list.
(3) When matching template arguments an explicit overload/specialization of "int()" binds stronger than "int(T)" (where T <- void)

I should have maybe mentioned this in the proposal. I suggested a solution sort of similar to this in the middle of the long thread "Allow values of void," but I've since decided it's somewhat questionable (I further suggested generalizing it so that any "empty" argument, regardless of the location in the argument list, can be considered to be equivalent to doing void{}, making something like foo(5, 'a', , 3.0) valid, with the third argument being void{}). The difference between what you are suggesting and what I was suggesting is that I still think that we'd need to keep the function types separate between int foo(void) and int foo(). If we didn't, we could have really subtle differences between void and other types here that can still break generic code. With the function types being equal, for instance, if you had both of those declarations you'd now be producing two overloads for all types T except void, but for void it would re-declare the same function and can cause other problems, such as if those declarations were both definitions. When void is a dependent type there, this becomes particularly subtle and can require special-casing. As well, if you agree with my rationale for why the function declarations would need to be separate types, then this also now implies that existing code is potentially broken, since right now it is allowable to do: void foo(); void foo(void) {} and the definition is the definition of the declared function. If we changed this, it means that any existing code that does this would now likely break. This can really be bad if the code that does this is a part of a compiled dependency.

In addition to that, the other reason I strayed from this type of approach is that it requires an altered kind of overload set to be considered when there is a single argument of type void. So there is more special-casing on the implementation side, and potentially this needs to be understood by callers to avoid subtleties. Ultimately, the language would be simpler without this rule, and I think if we designed the language from scratch we wouldn't have such a rule, so we should ideally try to more directly get what we want, only accounting for compatibility in the places where it is strictly necessary. I don't think we gain much by trying to do this sort of thing, but we definitely make the language more complex and add some subtleties that are special to void.

On Mon, Oct 5, 2015 at 1:38 PM, Miro Knejp <miro....@gmail.com> wrote:
Does this make sense?

Yes. I may bring this up at the meeting and I don't know why I left it out of the paper since I did seriously consider it early on. My thoughts at the moment are still that we should avoid this, but that was not always my intuition.

Casey Carter

unread,
Oct 5, 2015, 5:40:59 PM10/5/15
to ISO C++ Standard - Future Proposals
Sorry, I meant "all existing functions with return type void that return with no value, or by flowing off the end of the function, will have undefined behavior with the proposed change." Obviously there are currently valid cases in the language where return can be used with an expression of void type that would continue to have defined behavior. 

Matt Calabrese

unread,
Oct 5, 2015, 5:44:13 PM10/5/15
to ISO C++ Standard - Future Proposals
On Mon, Oct 5, 2015 at 1:59 PM, Nicol Bolas <jmck...@gmail.com> wrote:
Pointers not being valid iterators breaks pretty much the world at this point. At the very least, you'd royally honk-off SG14 if you suddenly declare that std::vector and std::array cannot use pointers for their iterators.

To be clear, I don't recommend making pointers not valid iterators, I just mean separating out the abstractions. For example, already in C++ a std::vector's iterator type may or may not be a pointer, though it can be a pointer. In a sort of similar fashion, in my hypothetical future proposal, an array iterator for most types may be the same type as the iterator type, however, in the case of a size 0 type, the iterator type would end up being different. In other words, if we allowed size 0 types, generic code that deals with things like arrays shouldn't be using T* as the iterator type directly, but rather, it should use an iterator type that may end up being a pointer type in most cases, but isn't required to be.

Again, that is a much bigger alteration to the standard and requires people to understand and make these changes in their own generic code, and the benefits, imo, are much less than some people imagine. Whether it's viable or not, or whether people even share my view on such a hypothetical change, are outside of the scope of the "Regular Void" proposal. Unless someone sees some simple way to solve all of these problems with respect to size 0 types, I think it's best that we punt on the idea until some future point in time. It doesn't impact the usability of void in generic code, it just means in practice that in some very special cases, likely the product of metaprogramming, we'd have slightly less memory-efficient types unless people take special consideration (similar to the types of considerations people make when exploiting the empty base optimization in generic code). I think that's acceptable for the time being until/unless someone tackles the harder problem of properly retrofitting support for size 0 types into the language. I don't have particularly high hopes for that right now, but who knows?

On Mon, Oct 5, 2015 at 1:59 PM, Nicol Bolas <jmck...@gmail.com> wrote:
Well, when I was working through the issues with defining stateless types (which are guaranteed to not take up space as members or base-classes of a struct), I used the general rule that the type should work like regular types as much as possible. They would have a non-zero size, for example, so you could legitimately allocate them dynamically on the heap if you had some reason to do so.

I think if we get size 0 types, you'd still be able to "allocate" them on the heap in the sense that the operation would be a valid operation. I don't think there is anything in conflict there. The main thing that is in conflict with respect to C++ right now are assumptions surrounding pointers, aliasing, etc..
 
On Mon, Oct 5, 2015 at 1:59 PM, Nicol Bolas <jmck...@gmail.com> wrote:
I ran into a fundamental problem with arrays through. At some point, you have to violate something. These were the options:

1) stateless members that are arrayed take up space (thus sometimes violating the "don't take up space" principle)

2) stateless members that are arrayed all have the same address (thus violating the rules of pointer arithmetic and arrays)

3) stateless members cannot be arrayed (thus violating the rule of behaving like regular types)

4) stateless types actually have sizeof return 0 (thus violating... well, all kinds of things)

I considered #3 to be the less onerous solution. We both seemed to come to the same conclusion on that one.

Right, this is why my personal conclusion is that, in generic code, we really shouldn't be assuming that a pointer is an iterator into an array. It should be a separate abstraction. In other words, if we started from scratch with the language, I'd say that we'd have pointers that you cannot do any arithmetic on at all, and we'd have array iterators which you could do pointer arithmetic on, and they'd be different types (maybe with explicit convertibility in some cases). In practice, the implementation of an array iterator would be exactly the same in most cases, though for types of size 0, the iterator would be more like an integer. We'd also drop the assumption of an object's type + address being unique. Hypothetically, all instances, whether on the stack, heap, namespace scope, etc. could share the same address and it wouldn't matter. We'd just have been starting without the assumption that an address is meaningful as a part of the object's identity. Unfortunately, I'm not too optimistic that we'd be able to move the language in this direction even though I genuinely believe that if we'd started from scratch this would be ideal.

Matt Calabrese

unread,
Oct 5, 2015, 5:52:31 PM10/5/15
to ISO C++ Standard - Future Proposals
I didn't change anything that would [directly] affect the special-casing of functions returning void in this respect. Is there something specific in my listed changes that would make this no longer true that you can point to, or is otherwise implied? 

Miro Knejp

unread,
Oct 5, 2015, 6:06:55 PM10/5/15
to std-pr...@isocpp.org
Am 05.10.2015 um 23:39 schrieb 'Matt Calabrese' via ISO C++ Standard - Future Proposals:

I should have maybe mentioned this in the proposal. I suggested a solution sort of similar to this in the middle of the long thread "Allow values of void," but I've since decided it's somewhat questionable (I further suggested generalizing it so that any "empty" argument, regardless of the location in the argument list, can be considered to be equivalent to doing void{}, making something like foo(5, 'a', , 3.0) valid, with the third argument being void{}). The difference between what you are suggesting and what I was suggesting is that I still think that we'd need to keep the function types separate between int foo(void) and int foo(). If we didn't, we could have really subtle differences between void and other types here that can still break generic code. With the function types being equal, for instance, if you had both of those declarations you'd now be producing two overloads for all types T except void, but for void it would re-declare the same function and can cause other problems, such as if those declarations were both definitions. When void is a dependent type there, this becomes particularly subtle and can require special-casing. As well, if you agree with my rationale for why the function declarations would need to be separate types, then this also now implies that existing code is potentially broken, since right now it is allowable to do: void foo(); void foo(void) {} and the definition is the definition of the declared function. If we changed this, it means that any existing code that does this would now likely break. This can really be bad if the code that does this is a part of a compiled dependency.
Hmm maybe I didn't get this over well enough. In my approach "void foo(); void foo(void) { }" defines the *same* function. There is no breaking here. My implication is that we treat any function that is declared/defined as "void foo()" as if it were declared/defined as "void foo(void)". The two refer to the same entity and are interchangeable. That alone would break existing code at the call site, unless we also say that calling a "void foo(void)" function as "foo()" is equivalent to "foo(void{})".

The (I think) only point where treating "void()" and "void(void)" as identical signatures would break is in in template argument matching:

template<class R>
void foo(std::function<R()> f);

template<class R, class T>
void foo(std::function<R(T)> f);

Here we either have to specify that the "R()" overload binds stronger than "R(T)" (where T=void), or that the signature "R()" is a shorthand for "R(void)" (as it is in function decls/defs), because that is how existing code works. I prefer the latter option. New code would ideally no longer require the "R()" overload at all.

In summary, I propose to make "void()" just a typing-lazy shorthand for "void(void)" in all contexts. It makes existing functions immediately compatible with new libraries. What I am not sure about is how frequently the expression "foo()" is used in SFINAE checks to filter out nullary function types, as those might break under this change. I cannot imagine a use case that wouldn't be solved easier with regular good ol' overload matching though.


In addition to that, the other reason I strayed from this type of approach is that it requires an altered kind of overload set to be considered when there is a single argument of type void. So there is more special-casing on the implementation side, and potentially this needs to be understood by callers to avoid subtleties. Ultimately, the language would be simpler without this rule, and I think if we designed the language from scratch we wouldn't have such a rule, so we should ideally try to more directly get what we want, only accounting for compatibility in the places where it is strictly necessary. I don't think we gain much by trying to do this sort of thing, but we definitely make the language more complex and add some subtleties that are special to void.
I agree that not having this rule would be better, but as with many things in C++, we can teach people to not use old constructs that have new and better alternatives. We can teach them that void foo() is really just a shorthand for void foo(void) and that the remaining language "just works".

Matt Calabrese

unread,
Oct 5, 2015, 6:53:26 PM10/5/15
to ISO C++ Standard - Future Proposals
On Mon, Oct 5, 2015 at 1:53 PM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
I still think trying to solve the problem that templates only
handle the one-value case by removing the zero case from the existing
zero-or-one that is presently allowed is the wrong solution (and wrong
direction, i.e. a regression rather than progress), and we should
instead be working towards zero-or-many support. Languages with first
class multiple return values and language level multi-value support
(e.g. unpacking) do much better in this realm.

On some level I get what you're saying, but in practice we have 0-N support by the way of tuples, and I think that is a reasonable solution (short of the practical difficulties regarding copy/move elision). If you compare C++ functions to functions in mathematics (normally a questionable comparison, but one that I think is fine in this particular context), this makes sense, too, where your functions always map to some result, unless they are undefined for certain input (analogous in C++ to input where preconditions are violated). In mathematics, returning "multiple" values is really returning a single vector of values. You don't actually lose anything by that abstraction (I'd personally argue that you gain), it's just that when you represent a function returning some fixed N different values in C++, you analogously return it as a tuple of size N, where it's perfectly acceptable for N to be 0. Further, even if we had a language-level solution for returning N different values, you'd still want to be able to pass these entities around as a single object without unpacking them as separate arguments, otherwise examples like my second logging example (the one with the verbosity option) break down. As well, without being able to pass around such entities without unpacking them, generic code would become difficult to write regarding forwarding of function return values to functions as arguments, especially when such function results would appear in the middle of a parameter list, and even more so if two different kinds of results appear as parameter types of the same function (both of these can easily come up in generic code). It's a much simpler abstraction to deal with if the representation of 0-N results is just a single object that contains 0-N components. Notably, with tuples like we already have, this would work, because tuples are already object types that you can pass around as a single entity (and you can unpack them, though with some more effort than what a language-level facility can provide). Either way, that top-level notion of the result being a single entity that can be passed around as a whole like any other object is important.

In short, I'd argue that the language-level solution for 0-N return values that you describe would likely be equivalent in functionality and similar in usage tuples only with nicer syntax and possibly better ways of taking advantage of copy/move elision. In practice they'd need to be a single, first class object that may have multiple components (where "multiple" can happen to be 0), not dissimilar from how I suggest void should be a first-class object. You'd still want to have that top-level single object abstraction regardless, and you'd want to be able to "store" the result on the stack, pass it around packed or unpacked, and deal with it in all of the same ways that you can deal with any other object in C++.

Finally, let's say we had hypothetical functions that could return 0-N values by way of something like a language-level tuple-like construct. Even in this case, I'm not certain that the implication is that "void" would or should be equivalent to the "tuple with no elements" type. The reason I say this is because in generic code that may yield 0-N results, the result would be expressed using some tuple-like syntax (though now at the language level), and an "empty" tuple there wouldn't usually appear simply as a type written as "void," but rather it would end up looking much more like current-day tuple<T...>., where sizeof...(T) would happen to be 0. If we wanted void to be that empty tuple type, we'd really just be specifying it as an alias to this type. At that point, I'd argue that we're not really gaining anything from this alias, because users could just use the type directly, and there's no reason to believe that we need these two types to be the same or that void should logically be considered to be an empty tuple as opposed to a monostate type. So even if we had language-level support for 0-N return values, I'd argue that it's not necessarily true that void should mean a 0-sized list of return types .
 
On Mon, Oct 5, 2015 at 1:53 PM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
- How do we deal with the implications of claiming that void is a
regular type, when a void function *has no return value* (keep in mind
this may have ABI and/or register allocation implications).

The changes I've proposed should not be ABI breaking in practice, which I briefly mention in the proposal.

On Mon, Oct 5, 2015 at 1:53 PM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
- If at some point we add first class multiple return values from
functions, how will we reconcile that 'void foo()' returns *zero* values
and not one value? Related, how do we explain away 'return;' as a
zero-value return in a function whose return type is specified as
"void", especially in the face of syntax to actually return multiple values?

Assuming you don't completely disagree with my above analysis, I think that this is not actually an issue. If you still think it is somehow an issue, I suggest that you provide some more grounded examples in code as to why you are unable to represent something.

On Mon, Oct 5, 2015 at 1:53 PM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
I don't see in the changes how you reconcile claiming that void is a
regular type with the fact that a void function *doesn't return a
value*, or for that matter how 'void foo() { return; }' is still
supposed to work.

"return;" still works perfectly fine in this proposal. It's effectively just syntactic sugar for "return void{};" If we were to design the language from scratch, maybe we wouldn't have this (in practice it's fine with or without this, unless you really want a non-explicit return of such a function to still be UB, which isn't an unreasonable consideration), but it's not actually an issue. Again, this is just syntactic sugar and also does not impact the ability to write proper generic code.

Matt Calabrese

unread,
Oct 5, 2015, 7:10:33 PM10/5/15
to ISO C++ Standard - Future Proposals
On Mon, Oct 5, 2015 at 3:07 PM, Miro Knejp <miro....@gmail.com> wrote:
Am 05.10.2015 um 23:39 schrieb 'Matt Calabrese' via ISO C++ Standard - Future Proposals:

I should have maybe mentioned this in the proposal. I suggested a solution sort of similar to this in the middle of the long thread "Allow values of void," but I've since decided it's somewhat questionable (I further suggested generalizing it so that any "empty" argument, regardless of the location in the argument list, can be considered to be equivalent to doing void{}, making something like foo(5, 'a', , 3.0) valid, with the third argument being void{}). The difference between what you are suggesting and what I was suggesting is that I still think that we'd need to keep the function types separate between int foo(void) and int foo(). If we didn't, we could have really subtle differences between void and other types here that can still break generic code. With the function types being equal, for instance, if you had both of those declarations you'd now be producing two overloads for all types T except void, but for void it would re-declare the same function and can cause other problems, such as if those declarations were both definitions. When void is a dependent type there, this becomes particularly subtle and can require special-casing. As well, if you agree with my rationale for why the function declarations would need to be separate types, then this also now implies that existing code is potentially broken, since right now it is allowable to do: void foo(); void foo(void) {} and the definition is the definition of the declared function. If we changed this, it means that any existing code that does this would now likely break. This can really be bad if the code that does this is a part of a compiled dependency.
Hmm maybe I didn't get this over well enough. In my approach "void foo(); void foo(void) { }" defines the *same* function. There is no breaking here. My implication is that we treat any function that is declared/defined as "void foo()" as if it were declared/defined as "void foo(void)". The two refer to the same entity and are interchangeable. That alone would break existing code at the call site, unless we also say that calling a "void foo(void)" function as "foo()" is equivalent to "foo(void{})".

Right, I think I understand what you're saying, but even this has implications in generic code. Consider the following:

//////////
template<class T>
struct foo
{
  foo() { /**/ } // default constructor
  foo(T init) : bar(std::move(init)) {}

  T bar;
};
//////////

If we were to assume that a single void parameter were equal to no parameters, then "foo" breaks down for the case that void is T, unnecessarily. I don't think we should be introducing these kinds of subtleties. Similarly, what is the arity of the function type "void(T)" where T is dependent. We now have the somewhat unexpected implication that the arity depends on what type "T" is. If we were to design the language from scratch we wouldn't be doing this and I don't think there are many tangible gains from making the language like this right now. We're not really getting additional compatibility. It would only really help APIs that would be transitioning from the old meaning of void to the new meaning of void (for instance, it could potentially make it easier when transitioning the std::promise API with respect to the removal of the specialization, but there are alternatives that also aid in that transition that do not require introducing more special-case rules for void). I think simply deprecating the current meaning is better and will more likely lead us closer to an ideal solution in the long term. It's also notably less complex to specify in the language if void has no special-casing for things like this.
 
On Mon, Oct 5, 2015 at 3:07 PM, Miro Knejp <miro....@gmail.com> wrote:
The (I think) only point where treating "void()" and "void(void)" as identical signatures would break is in in template argument matching:

template<class R>
void foo(std::function<R()> f);

template<class R, class T>
void foo(std::function<R(T)> f);

Here we either have to specify that the "R()" overload binds stronger than "R(T)" (where T=void), or that the signature "R()" is a shorthand for "R(void)" (as it is in function decls/defs), because that is how existing code works. I prefer the latter option. New code would ideally no longer require the "R()" overload at all.

These are really subtle problems that we'd avoid by just not treating void particularly special. I don't think introducing more special rules for void will be good in the long term, and I'm not convinced it even really helps much in the short term.

Larry Evans

unread,
Oct 6, 2015, 6:59:39 AM10/6/15
to std-pr...@isocpp.org
On 10/05/2015 04:39 PM, 'Matt Calabrese' via ISO C++ Standard - Future
Proposals wrote:
> On Mon, Oct 5, 2015 at 1:38 PM, Miro Knejp <miro....@gmail.com
> <mailto:miro....@gmail.com>> wrote:
>
> You specifically mention possibly re-introducing the int "foo(void)"
> syntax but note that it may create problems because it isn't an
> unary function. Now I think this can be solved with three
> adjustments, let me see if I missed anything:
>
>
> I think you get it. To restate, the idea is that right now, void
> foo(void) is equivalent to void foo(), which we'd want to change. void
> foo(void) should really mean what it says, assuming that void is an
> object type (in other words it should be a unary function instead of a
> nullary function). I just don't propose that we change the meaning just yet.
[snip]
Wouldn't this allow:

foo(foo())

since foo consumes a void and produces the same type?


Casey Carter

unread,
Oct 6, 2015, 9:20:39 AM10/6/15
to ISO C++ Standard - Future Proposals
You've left the statement "A return statement with no operand shall be used only in a function whose return type is cv void, a constructor (12.1), or a destructor (12.4)." in stmt.return, but it's not clear what the effect of such a return should be. The final sentence clarifies the behavior: "Flowing off the end of a function is equivalent to a return with no value; this results in undefined behavior in a value-returning function." Since a function returning void is now a value-returning function, void f(bool b) { if (b) return; } has undefined behavior for exactly the same reasons as std::nullptr_t g(bool b) { if (b) return; }.

Matthew Woehlke

unread,
Oct 6, 2015, 10:01:06 AM10/6/15
to std-pr...@isocpp.org
Saying "it's there" is nice, but doesn't answer the question. Can you
please point out where the proposal makes the necessary changes for
this? (Or explain why no changes are needed?)

--
Matthew

Matthew Woehlke

unread,
Oct 6, 2015, 10:14:56 AM10/6/15
to std-pr...@isocpp.org
On 2015-10-05 18:53, Matt Calabrese wrote:
> Further, even if we had a language-level solution for returning N
> different values, you'd still want to be able to pass these entities
> around as a single object *without* unpacking them as separate
> arguments, otherwise examples like my second logging example (the one
> with the verbosity option) break down.

True, and that's similarly why I think that something like:

void foo();
auto x = foo();

...should be supported in any case. But *not* by making void a regular
type. There should be just as much difference between assigning to a
single variable from a 0-value function vs. a 1-value function as from
an N-value function vs. a 1-value function (for N > 1).

For what it's worth, I was strongly in favor of being able to treat
MRV's as a single object. There was also, however, some strong opposition.

> Even in this case, I'm not certain that the implication is that
> "void" would or should be equivalent to the "tuple with no elements"
> type.

I would find it very strange for it to mean otherwise, when this is what
it has meant historically.

Incidentally, I'm sure we would want some way to force a 1-value result
to be "packed" the same as an N-value result. We can't always pack it
for the sake of compatibility, but generic code would need a way to
ensure consistency.

--
Matthew

Miro Knejp

unread,
Oct 6, 2015, 10:50:24 AM10/6/15
to std-pr...@isocpp.org
Whether one says “a function returns no value” or “a function returns a value that represents nothing” is a matter of perspective and only relevant at the language level. In a functional environment functions have to return *something* to the caller, otherwise they terminate in a different manner (like throwing). The unit type represents that the function finished its side effects and terminated by returning to the caller.

void as a regular type compared to an empty user-defined struct has the advantage that it is builtin and the compiler can sprinkle some fairy dust to make it disappear from the assembly as long as you don’t take its address or make it a member variable. If the user-defined empty struct is trivial same applies under the as-if rule.

There is no point passing a regular void in registers because it has only *one* possible value. It is not a value you need to waste bits in a register on, because the only value it can have is exactly “void{}” and nothing else. There is no actual bit-representable information that is returned from a function that returns a unit-type void. The fact the function returns means it produced a void value. If you need it, the compiler can create it out of thin air as it is just symbolic. Same goes for calling functions with a unit-type void. On the hardware level you aren’t passing any bit-representable information to the function. The fact the function executes means it got passed the void argument. If it needs to take its address the compiler can again create it out of thin air.


You can see that the implementation of foo() has no assembly to generate a return value and the assignment to the volatile variable just takes the address of a stack location with undefined content, which is fine because the actual bit pattern of X has no meaning.

If you uncomment the other stuff you can even see that the call to operator<< receives a single argument (the ostream), not two. The compiler decided to not pass an X to the function at all.

This shows that at least to this compiler an empty trivial user-defined struct has the same semantics as void when it comes to generation of call/return code. Therefore making void regular changes nothing in this context.

I don’t know if any ABIs support omitting empty structures so the compiler doesn’t have to waste stack space on arguments, but since unit-type void is builtin it can be ignored in calls and returns the same way it already is.

Miro Knejp

unread,
Oct 6, 2015, 10:54:07 AM10/6/15
to std-pr...@isocpp.org
On 06 Oct 2015, at 01:10 , 'Matt Calabrese' via ISO C++ Standard - Future Proposals <std-pr...@isocpp.org> wrote:

On Mon, Oct 5, 2015 at 3:07 PM, Miro Knejp <miro....@gmail.com> wrote:
Am 05.10.2015 um 23:39 schrieb 'Matt Calabrese' via ISO C++ Standard - Future Proposals:

I should have maybe mentioned this in the proposal. I suggested a solution sort of similar to this in the middle of the long thread "Allow values of void," but I've since decided it's somewhat questionable (I further suggested generalizing it so that any "empty" argument, regardless of the location in the argument list, can be considered to be equivalent to doing void{}, making something like foo(5, 'a', , 3.0) valid, with the third argument being void{}). The difference between what you are suggesting and what I was suggesting is that I still think that we'd need to keep the function types separate between int foo(void) and int foo(). If we didn't, we could have really subtle differences between void and other types here that can still break generic code. With the function types being equal, for instance, if you had both of those declarations you'd now be producing two overloads for all types T except void, but for void it would re-declare the same function and can cause other problems, such as if those declarations were both definitions. When void is a dependent type there, this becomes particularly subtle and can require special-casing. As well, if you agree with my rationale for why the function declarations would need to be separate types, then this also now implies that existing code is potentially broken, since right now it is allowable to do: void foo(); void foo(void) {} and the definition is the definition of the declared function. If we changed this, it means that any existing code that does this would now likely break. This can really be bad if the code that does this is a part of a compiled dependency.
Hmm maybe I didn't get this over well enough. In my approach "void foo(); void foo(void) { }" defines the *same* function. There is no breaking here. My implication is that we treat any function that is declared/defined as "void foo()" as if it were declared/defined as "void foo(void)". The two refer to the same entity and are interchangeable. That alone would break existing code at the call site, unless we also say that calling a "void foo(void)" function as "foo()" is equivalent to "foo(void{})".

Right, I think I understand what you're saying, but even this has implications in generic code. Consider the following:

//////////
template<class T>
struct foo
{
  foo() { /**/ } // default constructor
  foo(T init) : bar(std::move(init)) {}

  T bar;
};
//////////
Ah well this is unfortunate. So much for that idea then.

Andrew Tomazos

unread,
Oct 6, 2015, 11:10:22 AM10/6/15
to std-pr...@isocpp.org
On Mon, Oct 5, 2015 at 8:43 PM, Matt Calabrese <cala...@google.com> wrote:
Following the "Allow values of void" discussion, I've put together a proposal[1] for updating void to be a Regular type.

I've thought this through a couple of times before.

I think void should represent the absence of type, rather than be a type that holds one value.  It is correctly, imho, incomplete.

I understand the extra work that has to be done occasionally in generic programming where you have to write a separate specialization to deal with void (and have done this personally more than once).

I do not think the time saved by this rare case, outweighs the time lost from either migration to a "regular" void, or the end result of the strange cases that come up:

   void p;
   void* q = &p;  // void* has special properties
   void A[10];
   std::vector<void> v;
   void f(void); // 0 or 1 parameter
   sizeof(void)
   SFINAE uses of void
   etc

It's a close call, but on the weight of it, the generic programming case (which afaik is the only pro) I find insufficiently motivating.

Nicol Bolas

unread,
Oct 6, 2015, 12:06:22 PM10/6/15
to ISO C++ Standard - Future Proposals
I find this paper to be an interesting examination of the problem. It attempts to make `void` as regular as one could reasonably expect such a type to be.

However, the question is this: do we want `void` to be regular, or do we want to avoid having to specialize templates for it in so many cases?

If the goal is to make `void` regular, then P0146 obviously fails. There are too many occurrences of `void` being irregular for users to be able to generally rely on type regularity.

I think that making `void` regular is simply the wrong goal. I really think the goal should be to minimize the number of times you need to specialize templates.

Take the logging function in P0147, for example:

// Invoke a callable, logging its arguments and return value.
template<class R, class... P, class Callable>
R invoke_and_log
(callable_log<R(P...)>& log, Callable&& callable, P... args) {
  log
.log_arguments(args...);
 
auto result = std::invoke(std::forward<Callable>(callable),
                            std
::forward<P>(args)...);
  log
.log_result(result);
 
return result;
}

This stores a return value, logs it, and then returns that value. By its very nature, it makes assumptions about the return value of `Callable`. The return value must be copy/moveable; it can't return an immobile type. But of course, that is the nature of the function, so users expect it.

However, this particular implementation of return value logging introduces an unnecessary construct: a named object of the return type. You could instead implement it like this:

// Invoke a callable, logging its arguments and return value.
template<class R, class... P, class Callable>
R invoke_and_log
(callable_log<R(P...)>& log, Callable&& callable, P... args) {
  log
.log_arguments(args...);

 
return log.log_result(
    std
::invoke(std::forward<Callable>(callable),
      std
::forward<P>(args)...));
}

By implementing it this way, you have removed the need for being able to have a named `void` value. All you need in this case are two things:

1) The ability to return the result of a function call, even if that result is `void`. I'm pretty sure we already have this.

2) The ability to pass the result of a function to another function, even if that result is `void`. In the `void` case, the argument should effectively disappear.

Oh, and `log.log_result` will need to have an overload that takes no parameters.

By examining the problem domain like this, I think you can find ways to avoid some of the more pernicious issues of `void` regularity. I'd guess you can avoid having named values of `void` type. Which also means you can avoid questions of getting references to `void`, passing them around as arguments, copy/move, and so forth.

Oh sure, `void` will still be quite irregular. But I think, with a few relaxed rules, template programmers will be able to avoid writing quite so many specializations for `void`.

Matt Calabrese

unread,
Oct 6, 2015, 2:26:30 PM10/6/15
to ISO C++ Standard - Future Proposals
On Tue, Oct 6, 2015 at 7:14 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
On 2015-10-05 18:53, Matt Calabrese wrote:
> Further, even if we had a language-level solution for returning N
> different values, you'd still want to be able to pass these entities
> around as a single object *without* unpacking them as separate
> arguments, otherwise examples like my second logging example (the one
> with the verbosity option) break down.

True, and that's similarly why I think that something like:

  void foo();
  auto x = foo();

...should be supported in any case. But *not* by making void a regular
type. There should be just as much difference between assigning to a
single variable from a 0-value function vs. a 1-value function as from
an N-value function vs. a 1-value function (for N > 1).

Why do you think these restrictions are somehow necessary or a good idea? All you are talking about is a subset of functionality and I've shown with examples why the additional functionality is useful in real world code (I'm surprised this even required being that explicit, since the usefulness of Regular types is well understood already). If you personally decided that you didn't like this functionality due to religious reasons, just don't use those functions.

This is getting way off topic and I'd really like to not keep going down this:

Even with your hypothetical return value proposal, what are you suggesting the type of decltype(bar()) would be in general, assuming nothing about whether bar() returns 0, 1, or N return values? Are you suggesting that decltype wouldn't work here? Can we no longer do that in generic code? If we can do that, would it yield a tuple? If a tuple, why would you not want this tuple to be Regular (assuming the components are Regular)? Library-level tuples certainly are. This is true even if the tuple contains no components. Would your facility be Regular except for when there happen to be no components? If decltype works but doesn't yield a tuple, what does it return there? A variadic list of types? Are people expected to wrap that in some kind of type-list/tuple to forward it along (making the target variadic isn't always an option, especially when you either don't have control of the template or when your template already takes multiple types, where each type could be a return type)? If I have code that takes a function object and runs it on some other thread, returning a future, are you suggesting that the promise is something other than std::promise<decltype(bar())> or would that just work without making promise variadic or something? What about more complicated templates where you'd expect to instantiate it with multiple return types from different functions? What overall functionality are you getting out of this over library-level tuples, other than the possibility of more opportunities for copy/move elision and perhaps simpler syntax? Functions in mathematics certainly don't require such direct mappings to multiple results directly in the notion of a function and effectively do what we already do -- we return a single object that is a tuple when we need 0-N separate results. I'd further argue that this is not a weakness in mathematics nor in C++, since all we are doing is saying that an ordered list of results is itself a first-class type, and can be dealt with in the same ways that your can deal with other types. I agree that language-level multiple return values and language level tuples would be cool (though I'm not entirely convinced they are necessary given that we have library-level tuples), but regardless, if we had language level support, in practice they would either directly be some kind of tuple type or they would be easily packed into tuples in order to be useful in generic code. In either case, it would be beneficial for the packed, tuple-like object to be Regular, even when that type just so happens to contain no components, just as is already the case with std::tuple.
 
On Tue, Oct 6, 2015 at 7:14 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
For what it's worth, I was strongly in favor of being able to treat
MRV's as a single object. There was also, however, some strong opposition.

Strongly opposed by ... this group or at a meeting? Is there even a proposal for this? Skimming through the recent mailings, at least, I see no multiple return value proposal, unless I missed it.
 
On Tue, Oct 6, 2015 at 7:14 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
> Even in this case, I'm not certain that the implication is that
> "void" would or should be equivalent to the "tuple with no elements"
> type.

I would find it very strange for it to mean otherwise, when this is what
it has meant historically.

Has it? There is no notion of some generalized 0-N result-like datatype in C+++, with the closest being an tuple, which is notably Regular, and void is certainly not an alias of tuple<> and this does not at all hurt generic code. As I described earlier, if you had some kind of 0-N result syntax, you also wouldn't expect for that syntax to end up to "void", would you? As in maybe the syntax for multiple returns is something like {<int, float>}, and in generic code you could hypothetically form it from a variadic expansion, etc. In this purely hypothetical example, {<>} is an empty tuple. Why would "void" necessarily be an alias of this type? What do you gain from that? Even if it were an alias of that, as described, why would you expect this tuple with no elements to not be Regular.

Anyway, this is getting way off topic. If we want to talk further about MRV, let's keep it to the other thread. I see little here that deals with potential issues of the proposal other than philosophical issues with how people choose to think of void.

On Tue, Oct 6, 2015 at 7:14 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
Incidentally, I'm sure we would want some way to force a 1-value result
to be "packed" the same as an N-value result. We can't always pack it
for the sake of compatibility, but generic code would need a way to
ensure consistency.

These problems exist only if we make them exist. We don't have this problem right now and what you are suggesting doesn't actually solve additional problems. N results can and is represented perfectly fine as a single result with N components. Again, MRV is a sound idea, but really it's no different in functionality from language-level tuples, and these problems do not exist in generic code that deals with tuples (nor would they with well designed MRV). Please though, let's stay more on the topic of this proposal rather than a hypothetical and separate language feature.

Matt Calabrese

unread,
Oct 6, 2015, 2:35:28 PM10/6/15
to ISO C++ Standard - Future Proposals
On Tue, Oct 6, 2015 at 8:10 AM, Andrew Tomazos <andrew...@gmail.com> wrote:
On Mon, Oct 5, 2015 at 8:43 PM, Matt Calabrese <cala...@google.com> wrote:
Following the "Allow values of void" discussion, I've put together a proposal[1] for updating void to be a Regular type.

I've thought this through a couple of times before.

I think void should represent the absence of type, rather than be a type that holds one value.  It is correctly, imho, incomplete.

Why? What does this get you and what would you hypothetically lose by adding functionality to the type. This wouldn't (realistically) break existing uses of void, all it does is make generic code easier to write.

On Tue, Oct 6, 2015 at 8:10 AM, Andrew Tomazos <andrew...@gmail.com> wrote:
I understand the extra work that has to be done occasionally in generic programming where you have to write a separate specialization to deal with void (and have done this personally more than once).

I do not think the time saved by this rare case, outweighs the time lost from either migration to a "regular" void, or the end result of the strange cases that come up:

   void p;
   void* q = &p;  // void* has special properties
   void A[10];
   std::vector<void> v;
   void f(void); // 0 or 1 parameter
   sizeof(void)
   SFINAE uses of void
   etc

There is nothing particularly strange about any of these. void f(void) and SFINAE are explicitly addressed, too -- what specifically do you disagree with about the analysis there? All of above types and sizeof are perfectly sensible, and they are all either explicitly or implicitly addressed in the proposal. void would just be a monostate type. If you take issue with the resolution of something, then say specifically what the issue is.

Matthew Woehlke

unread,
Oct 6, 2015, 2:53:39 PM10/6/15
to std-pr...@isocpp.org
On 2015-10-06 14:26, Matt Calabrese wrote:
> If you personally decided that you didn't like this functionality due
> to religious reasons, just don't use those functions.

It doesn't work that way. My concern is that if your proposal is
accepted, we lose the ability to express certain concepts that we can
currently express (i.e. void as not-a-type), and where, if (hopefully
when) we later want to add "real MRV support", we've painted ourselves
into a corner where we've lost the ability to use 'void' for 0-tuples as
it seems we would want to so do. The problem isn't that it will break my
legacy code, it's that it moves the language in a questionable direction
from which it is difficult to backtrack.

> This is getting way off topic and I'd really like to not keep going down
> this:

I don't see it as off topic at all. I see it as two possible directions
that the language could move in which are mostly mutually exclusive.

I will, however, refrain from discussing the other questions you asked,
which are indeed drifting quite far off topic. (I will say, however,
briefly, that one of the major arguments for language-level tuples /
MRV's is to improve generic programming abilities in the face of N-tuple
returns as well as unpacking the same as function arguments. The most
important argument however is RVO.)

> On Tue, Oct 6, 2015 at 7:14 AM, Matthew Woehlke wrote:
>> For what it's worth, I was strongly in favor of being able to treat
>> MRV's as a single object. There was also, however, some strong opposition.
>
> Strongly opposed by ... this group or at a meeting? Is there even a
> proposal for this?

This group. I'm not aware that there has been a proposal. Personally,
I'm less interested in MRV's as first class citizens than general
unpacking. (Part of the aforementioned strong opposition was to even
having such a concept as general unpacking, insisting instead that it
must be done with first class MRV's and MRV helper functions to do
unpacking. I continue to maintain that the problems are orthogonal and
can both be addressed without being detrimental to either problem domain.)

The level of contention on the list seems to have killed anything
getting further.

> On Tue, Oct 6, 2015 at 7:14 AM, Matthew Woehlke wrote:
>> I would find it very strange for [a void function to mean something
>> other than returning a 0-tuple, which] is what it has meant
>> historically.
>
> Has it?

Yes. See in particular the lack-of-return equivalence that mentions the
lack of a value, the general way in which the result of a void function
disappears and/or cannot be used, and the machine instructions emitted
in a void function. A non-void function has a return slot that is filled
with the function result. A void function has no return slot and nothing
is filled on return. If this doesn't describe the difference between a
1-tuple return and a 0-tuple return, I don't know what does.

> As I described earlier, if you had some kind of 0-N result syntax,
> you also wouldn't expect for that syntax to end up to "void", would you?

That's *exactly* what I *would* expect. 1-tuple returns should be
interchangeable with "traditional non-void returns" (absent syntax to
force interpretation as a tuple). Likewise, 0-tuple returns should be
interchangeable with "traditional void returns".

Otherwise, we might as well forget all about compatibility with legacy
code and start over from scratch.

The reason this is relevant is because the direction of your proposal is
opposed to this.

--
Matthew

Matt Calabrese

unread,
Oct 6, 2015, 3:10:09 PM10/6/15
to ISO C++ Standard - Future Proposals
On Tue, Oct 6, 2015 at 9:06 AM, Nicol Bolas <jmck...@gmail.com> wrote:
I find this paper to be an interesting examination of the problem. It attempts to make `void` as regular as one could reasonably expect such a type to be.

However, the question is this: do we want `void` to be regular, or do we want to avoid having to specialize templates for it in so many cases?

If the goal is to make `void` regular, then P0146 obviously fails. There are too many occurrences of `void` being irregular for users to be able to generally rely on type regularity.

Why do you think void is not Regular in this proposal? To be clear, "Regular" is with respect to generic programming (there is a paper linked in the references that describes Regular types, but they are also described in Stepanov's books, formally in the old C++0x proposal, and I'm sure the current Concepts TS will have such a concept if it doesn't specify one already). In brief, a Regular type just has proper copy/move semantics is comparable, and move is noexcept. This proposal makes void Regular, and it is simply a monostate type.
 
On Tue, Oct 6, 2015 at 9:06 AM, Nicol Bolas <jmck...@gmail.com> wrote:
I think that making `void` regular is simply the wrong goal. I really think the goal should be to minimize the number of times you need to specialize templates.

This is precisely how you accomplish that goal in a way that doesn't leave void as some kind of special type that needs explicit consideration in generic code. Treating void as special is exactly the problem. It doesn't have to be anything special. It can just be a Regular type and you're not losing anything by that. Being special in the sense of not supporting fundamental operations is what forces users to write code in odd ways and to special case.
 
On Tue, Oct 6, 2015 at 9:06 AM, Nicol Bolas <jmck...@gmail.com> wrote:
However, this particular implementation of return value logging introduces an unnecessary construct: a named object of the return type. You could instead implement it like this:

// Invoke a callable, logging its arguments and return value.
template<class R, class... P, class Callable>
R invoke_and_log
(callable_log<R(P...)>& log, Callable&& callable, P... args) {
  log
.log_arguments(args...);

 
return log.log_result(
    std
::invoke(std::forward<Callable>(callable),
      std
::forward<P>(args)...));
}


No, you cannot implement it like that in the current language because you can't pass a void expression as an argument. That would also require a language change. Further, why should the user have to write it in this very specific way just to account for void? It is logically equivalent for all other types, why should it break for void (it shouldn't)? You are only making things more difficult and you are quite literally gaining nothing with this restriction over simply having void be a Regular type. A Regular type provides a superset of this functionality and works like any object type in C++, because that is exactly what it would be.
 
On Tue, Oct 6, 2015 at 9:06 AM, Nicol Bolas <jmck...@gmail.com> wrote:
2) The ability to pass the result of a function to another function, even if that result is `void`. In the `void` case, the argument should effectively disappear.

I already address this explicitly in the proposal and why it doesn't work. Please read the proposal.

On Tue, Oct 6, 2015 at 9:06 AM, Nicol Bolas <jmck...@gmail.com> wrote:
By examining the problem domain like this, I think you can find ways to avoid some of the more pernicious issues of `void` regularity. I'd guess you can avoid having named values of `void` type. Which also means you can avoid questions of getting references to `void`, passing them around as arguments, copy/move, and so forth.

You are still forcing writers of generic code to have arbitrary considerations if they want to work with void. This also doesn't avoid questions like getting references to void, or forming arrays, or anything at all. These still frequently come up in generic code.

Miro Knejp

unread,
Oct 6, 2015, 4:03:49 PM10/6/15
to std-pr...@isocpp.org
Am 06.10.2015 um 20:53 schrieb Matthew Woehlke:
>> On Tue, Oct 6, 2015 at 7:14 AM, Matthew Woehlke wrote:
>>> I would find it very strange for [a void function to mean something
>>> other than returning a 0-tuple, which] is what it has meant
>>> historically.
>> Has it?
> Yes. See in particular the lack-of-return equivalence that mentions the
> lack of a value, the general way in which the result of a void function
> disappears and/or cannot be used, and the machine instructions emitted
> in a void function. A non-void function has a return slot that is filled
> with the function result. A void function has no return slot and nothing
> is filled on return. If this doesn't describe the difference between a
> 1-tuple return and a 0-tuple return, I don't know what does.
http://melpon.org/wandbox/permlink/llXEZOp9bescGXMy

Please point out the diff in machine instructions between make_X() and
make_void(), especially where make_X() fills the "return slot" with an X
instance.

"But X is trivial, the return register is simply not initialized, it's
still there in EAX".

http://melpon.org/wandbox/permlink/tQuh4TUUbzHX3V1A

Please point to the instructions where the result of make_X() is used as
argument to call consume_X() (compared to how the result of make_Y() is
used as argument to call consume_Y()).

And this is just with -O0. With -O1 "there is no X".

You see the compiler already is pretty smart about this, and those are
used-defined types. True, there is still stack space allocated for the
argument, which is not the case in a nullary function, but that is
simply because user defined types require storage. void is builtin, and
even if we make it regular the compiler still has more opportunities to
remove it than it has with user-defined types. This should also prove to
you that a function returning "old-school void" is ABI compatible to a
function returning "regular void".

Please do your research before making claims about generated machine
instructions.

Matthew Woehlke

unread,
Oct 6, 2015, 4:31:55 PM10/6/15
to std-pr...@isocpp.org
On 2015-10-06 16:04, Miro Knejp wrote:
> Please do your research before making claims about generated machine
> instructions.

An "interesting" (non-monostate¹) return value is necessarily returned
somehow (excluding fell-off-the-end bugs). A void return value doesn't
get storage, in a register or otherwise.

Please don't accuse me of incompetence over a claim that is correct
aside from very exceptional cases.

(¹ I'm also curious how many times you actually see functions returning
monostates in the wild... For that matter, if a monostate already
behaves exactly like you want void to behave, why do we need to change
anything? Just teach people to use std::monostate instead of void, and
problem solved.)

--
Matthew

Matt Calabrese

unread,
Oct 6, 2015, 4:37:26 PM10/6/15
to ISO C++ Standard - Future Proposals
On Tue, Oct 6, 2015 at 11:53 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
On 2015-10-06 14:26, Matt Calabrese wrote:
> If you personally decided that you didn't like this functionality due
> to religious reasons, just don't use those functions.

It doesn't work that way. My concern is that if your proposal is
accepted, we lose the ability to express certain concepts that we can
currently express

Post actual code examples of what functionality you claim you are losing.

 On Tue, Oct 6, 2015 at 11:53 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
(i.e. void as not-a-type), and where, if (hopefully
when) we later want to add "real MRV support", we've painted ourselves
into a corner where we've lost the ability to use 'void' for 0-tuples as
it seems we would want to so do.

Again, as I've already explained, you 1) wouldn't lose the ability to treat void as equivalent to a 0-tuple (though I additionally do refute your assertion that there necessarily should be equivalence, even though it would be okay to have such equivalence) and 2) Just like with existing tuple types, such as std::tuple, a tuple with no components, such as tuple<> is Regular. Why would your language-level tuple type be any different in that particular respect. tuple<> being Regular is important right now to generic code just as it would be if it were a language-level facility. Are you claiming that std::tuple<> is incorrect here? Why? When you are dealing with generic code that deals with N different values in the form of a tuple (or MRV), you shouldn't expect your code to require additional consideration simply to handle the 0 case in the event that you encounter it.

 On Tue, Oct 6, 2015 at 11:53 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote: 
The problem isn't that it will break my
legacy code, it's that it moves the language in a questionable direction
from which it is difficult to backtrack.

Again, how? As I pointed out, you could still get equivalence of void with your empty language-level tuple type with or without this proposal, and in practice, your language-level tuple would likely be Regular, just like std::tuple<> and just as I propose void to be. There is no incompatibility here at all.

 On Tue, Oct 6, 2015 at 11:53 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote: 
I will, however, refrain from discussing the other questions you asked,
which are indeed drifting quite far off topic. (I will say, however,
briefly, that one of the major arguments for language-level tuples /
MRV's is to improve generic programming abilities in the face of N-tuple
returns as well as unpacking the same as function arguments. The most
important argument however is RVO.)

Yes, I agree that that is the most important argument. I am not against language-level tuples or direct support for MRV, but understand that those ideas are not at all mutually exclusive with this proposal as you seem to be thinking that they are.
 
 On Tue, Oct 6, 2015 at 11:53 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote: 
The level of contention on the list seems to have killed anything
getting further.

I don't know about that. Write up a proposal and submit it.
 
 On Tue, Oct 6, 2015 at 11:53 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote: 
> On Tue, Oct 6, 2015 at 7:14 AM, Matthew Woehlke wrote:
>> I would find it very strange for [a void function to mean something
>> other than returning a 0-tuple, which] is what it has meant
>> historically.
>
> Has it?

Yes. See in particular the lack-of-return equivalence that mentions the
lack of a value, the general way in which the result of a void function
disappears and/or cannot be used, and the machine instructions emitted
in a void function.

We are just adding functionality, not that dissimilar from adding something like "emplace" to std::vector. Sure you can break hypothetical SFINAE constructs in that sense, but that is minute and such hypotheticals were never recommended ways of doing SFINAE. What currently cannot be done doesn't really matter much in practice, just like when adding functionality to any other data type. As well, "machine instructions emitted" don't even necessary have to change, not that this latter point matters at all with respect to the soundness of the idea.


 On Tue, Oct 6, 2015 at 11:53 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote: 
A non-void function has a return slot that is filled
with the function result. A void function has no return slot and nothing
is filled on return. If this doesn't describe the difference between a
1-tuple return and a 0-tuple return, I don't know what does.

Well then you don't. Again, a std::tuple<> (as in a tuple with no components in existing C++ today) is still a Regular object type that you can make an instance of, copy, compare, return from and pass to functions, etc.. void as I describe it is completely compatible with such a notion and similarly would be compatible with language-level tuples. Making a 0-tuple not even instantiable, which again, is completely against existing practice if you compare it to std::tuple<>, even if you choose to not acknowledge the use cases, just makes sensible code more difficult to write. We shouldn't be afraid of the 0 case. It's not different from any other N in this situation and treating it like it is somehow special in this regard only hinders development.
 
 On Tue, Oct 6, 2015 at 11:53 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote: 
That's *exactly* what I *would* expect. 1-tuple returns should be
interchangeable with "traditional non-void returns" (absent syntax to
force interpretation as a tuple). Likewise, 0-tuple returns should be
interchangeable with "traditional void returns".

Otherwise, we might as well forget all about compatibility with legacy
code and start over from scratch.

It doesn't break compatibility with legacy code as you describe it. If you think it does, then post an actual example.

 On Tue, Oct 6, 2015 at 11:53 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote: 
The reason this is relevant is because the direction of your proposal is
opposed to this.
 
Again, they are not at all mutually exclusive. 

Matt Calabrese

unread,
Oct 6, 2015, 4:47:16 PM10/6/15
to ISO C++ Standard - Future Proposals
On Tue, Oct 6, 2015 at 1:31 PM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
if a monostate already
behaves exactly like you want void to behave, why do we need to change
anything? Just teach people to use std::monostate instead of void, and
problem solved.

This is already explained in the proposal.

Miro Knejp

unread,
Oct 6, 2015, 5:26:23 PM10/6/15
to std-pr...@isocpp.org
Am 06.10.2015 um 22:31 schrieb Matthew Woehlke:
> On 2015-10-06 16:04, Miro Knejp wrote:
>> Please do your research before making claims about generated machine
>> instructions.
> An "interesting" (non-monostate¹) return value is necessarily returned
> somehow (excluding fell-off-the-end bugs). A void return value doesn't
> get storage, in a register or otherwise.
Do you know what the unit type is? This proposal and the attached
discussion is all about making void a unit type, therefore a *monostate*
as you persist on calling it, or a *regular empty trivial type*. I just
proved to you that at least one compiler does not waste cycles nor
registers on returning such types from functions and the generated
machine instructions do not differ from returning void in its current
form. It proves that making void into such a builtin regular/unit type
does not impact code generation and therefore is zero-overhead and ABI
compatible.
>
> Please don't accuse me of incompetence over a claim that is correct
> aside from very exceptional cases.
It was incorrect as proven by actual machine instructions generated by
an actual compiler using a non-void returning function as evidence to
the contrary. This "very exceptional case" is what this proposal is all
about.
>
> (¹ I'm also curious how many times you actually see functions returning
> monostates in the wild...
That is irrelevant as the entire crux of the proposal is to make void
such a "monostate". Point is, an *empty trivial type* returned from a
function or consumed by the caller generates the same code as a void
returning function with this compiler.

Regardless, all of that doesn't matter in the end. Since void is a
builtin type the compiler can chose to eliminate it from function calls
and not give it any register or stack space in generated code even if it
is a regular type with sizeof > 0 under the as-if rule. No wasted
cycles, no wasted registers, no ABI incompatibility.

Matt Calabrese

unread,
Oct 6, 2015, 6:20:23 PM10/6/15
to ISO C++ Standard - Future Proposals
Ah, yeah, you're right. That's a mistake.

Larry Evans

unread,
Oct 6, 2015, 8:23:50 PM10/6/15
to std-pr...@isocpp.org
On 10/06/2015 05:20 PM, 'Matt Calabrese' via ISO C++ Standard - Future
Why not have a different type than void to represent "no value".
Why not the bottom type:

https://en.wikipedia.org/wiki/Bottom_type

which is the categorical dual of the top or unit type.

>
> --
>
> ---
> You received this message because you are subscribed to the Google
> Groups "ISO C++ Standard - Future Proposals" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to std-proposal...@isocpp.org
> <mailto:std-proposal...@isocpp.org>.
> To post to this group, send email to std-pr...@isocpp.org
> <mailto:std-pr...@isocpp.org>.
> Visit this group at
> http://groups.google.com/a/isocpp.org/group/std-proposals/.


Richard Smith

unread,
Oct 6, 2015, 8:48:20 PM10/6/15
to std-pr...@isocpp.org
That doesn't help in this case; a function with the bottom type as its return type can never return.

which is the categorical dual of the top or unit type.

The top type is not a unit type (unless your type system is very uninteresting); every value is a value of the top type, whereas the unit type has only one value.

Nevin Liber

unread,
Oct 7, 2015, 10:50:29 AM10/7/15
to std-pr...@isocpp.org
On 5 October 2015 at 13:43, Matt Calabrese <cala...@google.com> wrote:

Given that void is all over the place in the C standard, which is the base document for C++, it is surprising not to find a detailed section on C Compatibility in your proposal.  Do you plan on adding one soon?
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Christopher Horvath

unread,
Oct 7, 2015, 12:27:34 PM10/7/15
to std-pr...@isocpp.org
On Wed, Oct 7, 2015 at 7:49 AM, Nevin Liber <ne...@eviloverlord.com> wrote:
On 5 October 2015 at 13:43, Matt Calabrese <cala...@google.com> wrote:

Given that void is all over the place in the C standard, which is the base document for C++, it is surprising not to find a detailed section on C Compatibility in your proposal.  Do you plan on adding one soon?

Given that C does not allow function overloads by name, and has no templates, am I wrong in thinking that the effects are essentially non-existent? 

 
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Nicol Bolas

unread,
Oct 7, 2015, 12:39:33 PM10/7/15
to ISO C++ Standard - Future Proposals
On Tuesday, October 6, 2015 at 3:10:09 PM UTC-4, Matt Calabrese wrote:
On Tue, Oct 6, 2015 at 9:06 AM, Nicol Bolas <jmck...@gmail.com> wrote:
I find this paper to be an interesting examination of the problem. It attempts to make `void` as regular as one could reasonably expect such a type to be.

However, the question is this: do we want `void` to be regular, or do we want to avoid having to specialize templates for it in so many cases?

If the goal is to make `void` regular, then P0146 obviously fails. There are too many occurrences of `void` being irregular for users to be able to generally rely on type regularity.

Why do you think void is not Regular in this proposal? To be clear, "Regular" is with respect to generic programming (there is a paper linked in the references that describes Regular types, but they are also described in Stepanov's books, formally in the old C++0x proposal, and I'm sure the current Concepts TS will have such a concept if it doesn't specify one already). In brief, a Regular type just has proper copy/move semantics is comparable, and move is noexcept. This proposal makes void Regular, and it is simply a monostate type.

I seem to have read your proposal backwards then. I was looking for the treatment of specific instances, so my searches all lead to the "alternative" section, which I mistook for the actual proposal.

Sorry about that.
 
 
On Tue, Oct 6, 2015 at 9:06 AM, Nicol Bolas <jmck...@gmail.com> wrote:
I think that making `void` regular is simply the wrong goal. I really think the goal should be to minimize the number of times you need to specialize templates.

This is precisely how you accomplish that goal in a way that doesn't leave void as some kind of special type that needs explicit consideration in generic code. Treating void as special is exactly the problem. It doesn't have to be anything special. It can just be a Regular type and you're not losing anything by that. Being special in the sense of not supporting fundamental operations is what forces users to write code in odd ways and to special case.

Wait: if the goal is to help improve template code writing, why does it matter if `void` is a special type or not?

My issue here is that you seem to be conflating two points, using one to hide your desire for the other. Your proposal is very clear on the motivations. It's not that `void` ought to be a type, for some functional programming reason or whatever.

The motivation is because, by making `void` regular, you make template programming easier in various situations. You don't have to make complex specializations and such just for a minor case of a function with no return value.

However, if the goal is making template code easier to write, then whether `void` is "some kind of special type that needs explicit consideration in generic code" is irrelevant. What matters is how hard it is to provide that "explicit consideration".

For example, P0128 (consexpr_if) solves your logging problem thusly:

// Invoke a callable, logging its arguments and return value.
template<class R, class... P, class Callable>
R invoke_and_log
(callable_log<R(P...)>& log, Callable&& callable, P... args) {
  log
.log_arguments(args...);

  constexpr_if
(is_same_v<void, std::result_of_t<Callable, Args...>>)
 
{
   
auto result = std::invoke(std::forward<Callable>(callable),
      std
::forward<P>(args)...);
    log
.log_result(result);
   
return result;
 
}
 
else
   
return;
}

This code looks perfectly reasonable. You could even write a metafunction to make the conditional a bit shorter, if that bothers you.

This is a solution to the problem that motivates your proposal. You probably won't prefer this solution. But you cannot deny that it substantially reduces the burden on users when doing this sort of stuff. It doesn't require adding function specializations and whatnot. It just works, and its easy to read and reason about.

So, do you want to make `void` regular, or do you want to help improve template code? Because the former is not required for the latter. You claim that `void` being special is "the problem", but it's not. "The problem" is the difficulty in writing any kind of conditional template code.

Solve that problem, and 80% of the reason for regularizing `void` goes away.

There is another problem with regularizing `void` that your proposal does not deal with. Namely, that you've removed a fundamental distinction between "functions that return a value" and "functions that don't return a value".

As an example of where this distinction is important, consider a specialized transformation visitor for a variant. That is, it converts one variant into another variant. And let's say we're clever and we decree that the typelist for the generated variant will be deduced from the return types of the visitor (I don't know if that's possible, but it sounds plausible).

Well, answer me this: what would it mean for the user to provide a `void` function in such a visitor? Did the user make a mistake? Or does the user actually mean to store the `void` value?

It would make far more sense for such a function to reject `void` functions. Why? Because thanks to auto return type deduction, it's very easy to compute a value and forget to return it, thus accidentally creating a `void` function. And sure, a compiler might offer a warning, but a compiler error is much more effective. If the user truly wanted an "empty" state, they'd use an empty class.

With your case, my visitation function needs to expend effort to break those using `void`. And without constexpr_if or concepts, that effort is non-trivial.

In short, not every user wants to treat `void` as regular. Why should regularity be the default case?

So on the one hand, your solution to the problem seems like overkill; we can solve the problem adequately without affecting `void`. And on the other hand, there are many cases where you don't want to treat `void` like any other type, and if you make `void` regular, you have to expend code to do that.

On Tue, Oct 6, 2015 at 9:06 AM, Nicol Bolas <jmck...@gmail.com> wrote:
By examining the problem domain like this, I think you can find ways to avoid some of the more pernicious issues of `void` regularity. I'd guess you can avoid having named values of `void` type. Which also means you can avoid questions of getting references to `void`, passing them around as arguments, copy/move, and so forth.

You are still forcing writers of generic code to have arbitrary considerations if they want to work with void. This also doesn't avoid questions like getting references to void, or forming arrays, or anything at all. These still frequently come up in generic code.

You would strengthen your motivation section if you could provide real-world example code of cases where these operations are necessary. It would also make it easy for a user to judge if the code is logically consistent.

It seems to me that most instances of such occurrences are when dealing with return values of user-provided functions. And thus, most such operations would naturally not make sense when dealing with user-provided functions that have no return value. So code that builds an array of return values from functions that don't have a return value seems... odd. That sounds like the user provided the wrong function, which should be a compiler error.

Christopher Horvath

unread,
Oct 7, 2015, 12:59:43 PM10/7/15
to std-pr...@isocpp.org


My issue here is that you seem to be conflating two points, using one to hide your desire for the other. Your proposal is very clear on the motivations. It's not that `void` ought to be a type, for some functional programming reason or whatever.

The motivation is because, by making `void` regular, you make template programming easier in various situations. You don't have to make complex specializations and such just for a minor case of a function with no return value.

However, if the goal is making template code easier to write, then whether `void` is "some kind of special type that needs explicit consideration in generic code" is irrelevant. What matters is how hard it is to provide that "explicit consideration".


The argument you're presenting in your various posts on this topic seems to take, as a base assumption, that these "special handling for void" cases happen very rarely, and are not that big a deal.  I write significantly less library-level and fully generic code than many of the people on this list, and yet I've run into this problem many times. It doesn't seem infrequent, and it is always tedious.  Matt's proposal would already help simplify the code I've had to write.  Particularly when I have compounded, or nested structures and functions.

You seem to be very passionately arguing against this suggestion based on the statement that people are using 'void' to mean the absence of a type, as opposed to a monostate type, and yet you don't provide any real, complete examples of code that do in fact require the distinction you're defending.  In fact, Miro Knejp has demonstrated precisely that for 'normal users', no problems will occur, and has shown that the theoretical foundation for this proposal is strong.

Can you please provide real code examples that are not hypothetical contrivances that would be broken by this proposal? Otherwise, it seems like your objections are based on some individual interpretation or discomfort.

Chris


Nevin Liber

unread,
Oct 7, 2015, 1:01:54 PM10/7/15
to std-pr...@isocpp.org
On 7 October 2015 at 11:27, Christopher Horvath <black...@gmail.com> wrote:

Given that C does not allow function overloads by name, and has no templates, am I wrong in thinking that the effects are essentially non-existent? 

I have no idea.  Someone who wants this change to the language has to go through the C standard and figure that out.

For instance:

C11 6.2.5p19: The void type comprises an empty set of values; it is an incomplete object type that cannot be completed.

Surely that would no longer be true.  Are there any repercussions from that in the rest of the C standard?  Sure.  Now sizeof(void) compiles.

What if sizeof(void) > 1 (since the proposal has opted to leave the sizeof(void) unspecified)?  Could that violate

C11 6.2.5p28: A pointer to void shall have the same representation and alignment requirements as a pointer to a character type.

Etc. etc.

Given that this is a breaking change from C, the proposal author has to go through the C standard and document the repercussions so the committee can decide how significant those changes are.  Plus, all that would need to be documented in Annex C of the C++ Standard were such a proposal to pass.

Matt Calabrese

unread,
Oct 7, 2015, 1:54:01 PM10/7/15
to ISO C++ Standard - Future Proposals
On Wed, Oct 7, 2015 at 7:49 AM, Nevin Liber <ne...@eviloverlord.com> wrote:
On 5 October 2015 at 13:43, Matt Calabrese <cala...@google.com> wrote:

Given that void is all over the place in the C standard, which is the base document for C++, it is surprising not to find a detailed section on C Compatibility in your proposal.  Do you plan on adding one soon?

Define soon ;) I didn't plan on sending around a draft of a revision prior to Kona, if that's what you mean (though I suppose I can, if you want), but assuming the idea isn't completely thrown out at Kona and something like this ends up being required, I'll add a further section in a revision to this, in addition to the separate proposal for standard library changes (std::promise, etc.).

Matt Calabrese

unread,
Oct 7, 2015, 3:17:04 PM10/7/15
to ISO C++ Standard - Future Proposals
On Wed, Oct 7, 2015 at 9:39 AM, Nicol Bolas <jmck...@gmail.com> wrote:
On Tuesday, October 6, 2015 at 3:10:09 PM UTC-4, Matt Calabrese wrote:
On Tue, Oct 6, 2015 at 9:06 AM, Nicol Bolas <jmck...@gmail.com> wrote:
I find this paper to be an interesting examination of the problem. It attempts to make `void` as regular as one could reasonably expect such a type to be.

However, the question is this: do we want `void` to be regular, or do we want to avoid having to specialize templates for it in so many cases?

If the goal is to make `void` regular, then P0146 obviously fails. There are too many occurrences of `void` being irregular for users to be able to generally rely on type regularity.

Why do you think void is not Regular in this proposal? To be clear, "Regular" is with respect to generic programming (there is a paper linked in the references that describes Regular types, but they are also described in Stepanov's books, formally in the old C++0x proposal, and I'm sure the current Concepts TS will have such a concept if it doesn't specify one already). In brief, a Regular type just has proper copy/move semantics is comparable, and move is noexcept. This proposal makes void Regular, and it is simply a monostate type.

I seem to have read your proposal backwards then. I was looking for the treatment of specific instances, so my searches all lead to the "alternative" section, which I mistook for the actual proposal.

I could probably separate that out better. It's also been pointed out that I should probably inline an exact definition of Regular (as even that has gone through subtle revisions over time, especially since the language got move semantics). 
 
On Wed, Oct 7, 2015 at 9:39 AM, Nicol Bolas <jmck...@gmail.com> wrote:
On Tue, Oct 6, 2015 at 9:06 AM, Nicol Bolas <jmck...@gmail.com> wrote:
I think that making `void` regular is simply the wrong goal. I really think the goal should be to minimize the number of times you need to specialize templates.

This is precisely how you accomplish that goal in a way that doesn't leave void as some kind of special type that needs explicit consideration in generic code. Treating void as special is exactly the problem. It doesn't have to be anything special. It can just be a Regular type and you're not losing anything by that. Being special in the sense of not supporting fundamental operations is what forces users to write code in odd ways and to special case.

Wait: if the goal is to help improve template code writing, why does it matter if `void` is a special type or not?

It can be special in that it can do more, just like how a random access iterator is still a forward iterator and usable with functions that expect forward iterators, even though it can do more than a forward iterator. This is why things like the ability to write "return;" in a function that returns void, or that objects are explicitly convertible to void don't hinder writing generic code. Not being able to do things like copy them or make arrays or tuples of them, on the other hand, does actually hurt our ability to write generic code. As an example, imagine a generic algorithm that, as a part of its execution, executes N function objects that were provided by the user and packages up the return values, returning them as a tuple. There's not really anything logically weird about this. The user should be able to access the Nth return value via the Nth location in the tuple. They should be able to iterate over elements over the tuple, etc. If one of the functions happened to return void, then this is fine. In the generic algorithm it doesn't matter, even though many of the operations involved just happen to effectively be no-ops. On the user-side, they don't have to do anything particularly special to account for the fact that one of their functions returned void. All of this is fine. Instantiating, copying, and comparing instances of void in non-generic code isn't particularly interesting, but there's nothing incorrect about it, and as soon as void becomes a dependent type, you can encounter generic code that requires those operations, since in the generic case they don't know (and also shouldn't care) that things like copies are a no-op for your particular type.

On Wed, Oct 7, 2015 at 9:39 AM, Nicol Bolas <jmck...@gmail.com> wrote:
The motivation is because, by making `void` regular, you make template programming easier in various situations. You don't have to make complex specializations and such just for a minor case of a function with no return value.

However, if the goal is making template code easier to write, then whether `void` is "some kind of special type that needs explicit consideration in generic code" is irrelevant. What matters is how hard it is to provide that "explicit consideration".

It's that void shouldn't require explicit consideration at all. If you logically just represent it as a unit type, it's an object type like anything other object type. Indeed, some broader definitions of a void type even explicitly refer to it as a unit type, even though C++ currently doesn't specify it as such. Even if we had other ways that simply made it easier to handle void, think about what you'd specify as the concept requirements of your generic function that potentially involves void as a dependent type "T" -- specifically, what would the concept requirements of "T" be for your algorithm? "T must model the Regular concept... or it can be a void type" (the notion of "or" constraints are already pretty dubious in generic code to begin with). Similarly, this would be true of all other generic functions that need to deal with a Regular type but also want to account for void. So not only are you complicating the implementation of your function, but you're also complicating its specification. Why? Having void be a unit type removes all of this. It also simplifies the standard and how people need to think about types in C++.

On Wed, Oct 7, 2015 at 9:39 AM, Nicol Bolas <jmck...@gmail.com> wrote:

For example, P0128 (consexpr_if) solves your logging problem thusly:

// Invoke a callable, logging its arguments and return value.
template<class R, class... P, class Callable>
R invoke_and_log
(callable_log<R(P...)>& log, Callable&& callable, P... args) {
  log
.log_arguments(args...);

  constexpr_if
(is_same_v<void, std::result_of_t<Callable, Args...>>)
 
{
   
auto result = std::invoke(std::forward<Callable>(callable),
      std
::forward<P>(args)...);
    log
.log_result(result);
   
return result;
 
}
 
else
   
return;
}

This code looks perfectly reasonable. You could even write a metafunction to make the conditional a bit shorter, if that bothers you.

This is a solution to the problem that motivates your proposal. You probably won't prefer this solution. But you cannot deny that it substantially reduces the burden on users when doing this sort of stuff. It doesn't require adding function specializations and whatnot. It just works, and its easy to read and reason about.

Your example doesn't execute the function in the void case, nor does it log the result. Even if you made the proper fix here, that still requires explicit consideration for void, and it also makes the concept requirements of the function complicated, so I wouldn't consider it a solution to the problem. We already have existing solutions in the language today, without modification, that makes things analogous to what you show possible to write. You can even inline such solutions in the definition of the function template (if not clear, you can [partially] simulate static-if/constexpr_if by way of generic lambdas and forcing types involved to be dependent).

On Wed, Oct 7, 2015 at 9:39 AM, Nicol Bolas <jmck...@gmail.com> wrote: 
There is another problem with regularizing `void` that your proposal does not deal with. Namely, that you've removed a fundamental distinction between "functions that return a value" and "functions that don't return a value".

I've addressed this numerous times and why the concern is completely illusory. Since it keeps coming up, I'll add a section on it if there ends up being another revision.

On Wed, Oct 7, 2015 at 9:39 AM, Nicol Bolas <jmck...@gmail.com> wrote:  
As an example of where this distinction is important, consider a specialized transformation visitor for a variant. That is, it converts one variant into another variant. And let's say we're clever and we decree that the typelist for the generated variant will be deduced from the return types of the visitor (I don't know if that's possible, but it sounds plausible).

Well, answer me this: what would it mean for the user to provide a `void` function in such a visitor? Did the user make a mistake? Or does the user actually mean to store the `void` value?

??? Why do you think that's a problem. The overall result type (assuming some form of deduction) would be void or possibly would require the user to be explicit (though I'd personally suggest the former, but it doesn't really matter), which isn't especially different whether vodi is Regular or not. What do you think is weird here? If they provide a function that returns void it's not a mistake nowadays when doing visitation.
 
On Wed, Oct 7, 2015 at 9:39 AM, Nicol Bolas <jmck...@gmail.com> wrote:  
It would make far more sense for such a function to reject `void` functions. Why? Because thanks to auto return type deduction, it's very easy to compute a value and forget to return it, thus accidentally creating a `void` function. And sure, a compiler might offer a warning, but a compiler error is much more effective. If the user truly wanted an "empty" state, they'd use an empty class.

Not that what you are saying is particularly different whether or not void undergoes the proposed changes, are you really advocating that variant's visitation function shouldn't allow void return types, and further that this is also why void shouldn't be Regular!? As someone who has worked with variants and visitation in pretty much every project for very many years, visitors that return void are not at all uncommon, though it doesn't take experience to understand that. There is nothing logically incorrect about them. People "forgetting to write return" does not change whether void is Regular or if it were to stay just as it is today. If you want people to be explicit instead of relying on deduction in that void case, assuming you support deduction at all with the apply_visitor-like function template, you could always require the user to be explicit about the return type in those cases. This is true whether the proposal is accepted or not.

On Wed, Oct 7, 2015 at 9:39 AM, Nicol Bolas <jmck...@gmail.com> wrote:  
In short, not every user wants to treat `void` as regular. Why should regularity be the default case?

Because it is only additional functionality that you do not pay for. If you don't want that additional functionality then simply do not use it. People who write real world generic code care about this and it's even a simplification to the type system and the standard.

 On Wed, Oct 7, 2015 at 9:39 AM, Nicol Bolas <jmck...@gmail.com> wrote:  
So on the one hand, your solution to the problem seems like overkill; we can solve the problem adequately without affecting `void`.

I don't consider a simplification to the standard to be "overkill" and the other suggested solutions only add more special casing.

On Wed, Oct 7, 2015 at 9:39 AM, Nicol Bolas <jmck...@gmail.com> wrote:  
You would strengthen your motivation section if you could provide real-world example code of cases where these operations are necessary. It would also make it easy for a user to judge if the code is logically consistent.

Yeah, noted :/ I should have included a bunch of example uses.
 

On Wed, Oct 7, 2015 at 9:39 AM, Nicol Bolas <jmck...@gmail.com> wrote:  
It seems to me that most instances of such occurrences are when dealing with return values of user-provided functions. And thus, most such operations would naturally not make sense when dealing with user-provided functions that have no return value. So code that builds an array of return values from functions that don't have a return value seems... odd. That sounds like the user provided the wrong function, which should be a compiler error.

It's not particularly odd. Any generic function that executes some user-provided functions may want to forward back those results to the user in some way. If the user provided functions that happened to return void, then the user probably wouldn't touch those results (unless they are also in generic code). If the result wasn't void, the user might examine the results. The generic code isn't impacted, regardless. It's not very dissimilar from std::for_each returning the function object that was passed in. People pass in simple function objects to std::for_each that have no state very frequently (those function objects are monostate/unit types). The generic function still returns the function object back to the caller in the even that their function object did have some state that the user may with to examine. If there is no information that needs to be conveyed, then so what? There's no logical error there.

Nicol Bolas

unread,
Oct 8, 2015, 6:34:42 PM10/8/15
to ISO C++ Standard - Future Proposals
I think I can help strengthen the motivational case for this feature (despite the fact that I loathe the very concept of it).

I think the logging function is a terrible example, and it should not be the primary focus of the motivation section. Though it is a simple example, it is precisely the simplicity that's the problem. It's too easily dismissed, for three reasons:

1) The required code to support functions with no return value is, quite frankly, not that hard to write.

2) There are proposed language features that make it even easier to both write and read.

3) It raises the question of why a user would want to log `void` returns at all. The function call and it's parameters, yes. But does a user really want to see a bunch of "void"s in the log file? (FYI: in one of my earlier posts, I did forget the `invoke` call by mistake. But the lack of logging in the `void` case was very much deliberate.)

Instead, you should aim for something that clearly and strongly indicates the extent of the problem. Something that's almost irrefutable (and I should know, since I've been trying to refute it ever since I realized it). Your proposal talks about them a bit, but it focuses on users of them rather than those cases specifically. I really think you should build your central thesis on these templates:

std::promise and std::future

These are pretty much unbreakable. The `future` type, by its very nature, must have storage for its value. But, if it doesn't have a value... what does it store? Which means that you need to change more than just a function or two.

You have to write the class all over again. Which is of course what the standard requires.

Oh sure,  you can derive from a common base that contains the work not related to the value (and you'll be doing that anyway, since you have to support `R&`). But you have to write four sets of constructors (1 common base, 3 for the various specializations). You have to write three separate implementations of your getter function. And so forth.

Something similar goes for `promise`.

While the solution of a common base may mitigate some problems, it makes maintaining the code more complex. If the interface needs to grow, then you may need to implement this in 3 cases. You have to test your code against 3 possibilities. And so forth.

At the end of the day, it's a mess. And it's a mess that no language feature can easily work around. Concepts won't help. `constexpr_if` won't help. You just have to bite down.

Also, while one could argue that `promise<void>` doesn't make sense, allowing that specialization also clearly demonstrates the benefits of `void` regularity. It allows you to have a consistent interface to an important concept: whether a process has completed. One could try to argue that we could have had `task` and `promise<NotVoid>` as separate types, but the only difference between them is that one has a value and the other does not. In every other way, they'd be identical.

`promise` became a more useful type because effort was expended to make `void` seem regular (that is, as regular as it could be without a language change).

Examples like those are your best bet here. It almost convinced me that it's a good idea.

Matt Calabrese

unread,
Oct 8, 2015, 6:42:38 PM10/8/15
to ISO C++ Standard - Future Proposals

--

Matt Calabrese

unread,
Oct 8, 2015, 7:09:04 PM10/8/15
to ISO C++ Standard - Future Proposals
Woops, ignore the previous reply. I somehow accidentally sent immediately.

On Thu, Oct 8, 2015 at 3:34 PM, Nicol Bolas <jmck...@gmail.com> wrote:
I think I can help strengthen the motivational case for this feature (despite the fact that I loathe the very concept of it).

I think the logging function is a terrible example, and it should not be the primary focus of the motivation section. Though it is a simple example, it is precisely the simplicity that's the problem. It's too easily dismissed, for three reasons:

1) The required code to support functions with no return value is, quite frankly, not that hard to write.

2) There are proposed language features that make it even easier to both write and read.

3) It raises the question of why a user would want to log `void` returns at all. The function call and it's parameters, yes. But does a user really want to see a bunch of "void"s in the log file? (FYI: in one of my earlier posts, I did forget the `invoke` call by mistake. But the lack of logging in the `void` case was very much deliberate.)

If you have a suggestion for a different example, then I'm open to it assuming this manages to make it to another revision, though if it makes it that far, I imagine that the motivation will have been recognized as sufficient, so it might end up being moot.

FWIW I do explicitly explain why logging the result is important directly in the proposal, and I think of the subtlty as a part of its strength (in the real world code that this is based on, I too initially started with a solution that didn't "log" the void result, just as you did). The serialization of "void" can even be a no-op, so it has no implications of occupying storage in a log. The reason you generally still want the function to be called is that "log_result" not only serializes the value, but the fact that it is called at all signifies that there was a result (as in, an exception didn't propagate and there wasn't some other form of termination when invoking the function that we are logging).

I also really wanted at least one example that is separate from the standard library just to make it clear that this is something that everyday users face and not just standard library implementors. Maybe there's a better example than logging, but it is one of the real world scenarios I've encountered, and I'd like at least something separate from what might be considered an implementation curiosity of the standard library.

On Thu, Oct 8, 2015 at 3:34 PM, Nicol Bolas <jmck...@gmail.com> wrote:
std::promise and std::future

These are pretty much unbreakable. The `future` type, by its very nature, must have storage for its value. But, if it doesn't have a value... what does it store? Which means that you need to change more than just a function or two.

You have to write the class all over again. Which is of course what the standard requires.

Oh sure,  you can derive from a common base that contains the work not related to the value (and you'll be doing that anyway, since you have to support `R&`). But you have to write four sets of constructors (1 common base, 3 for the various specializations). You have to write three separate implementations of your getter function. And so forth.

Something similar goes for `promise`.

While the solution of a common base may mitigate some problems, it makes maintaining the code more complex. If the interface needs to grow, then you may need to implement this in 3 cases. You have to test your code against 3 possibilities. And so forth.

At the end of the day, it's a mess. And it's a mess that no language feature can easily work around. Concepts won't help. `constexpr_if` won't help. You just have to bite down.

Right, and this proposal would solve that problem. I suppose I could have focused more time explicitly on this and other examples, but it was somewhat last minute (down to the hour) and I decided to focus more effort on the issues with alternative suggested solutions (I anticipated that some of those alternatives would come up in discussion, as they did in this group, so it made sense to have an analysis of some of them upfront). I assumed that those in the committee would immediately relate to the std::promise/std::future issue, but I suppose that might not be a correct assumption. I have a partially written paper specifically about std::promise/std::future and other standard library changes that would accompany this change, and I was hoping to submit it at the same time as this proposal, but it would have taken too much time to complete and would be something of a wasted effort if the overall idea were shot down at the language level, anyway.

On Thu, Oct 8, 2015 at 3:34 PM, Nicol Bolas <jmck...@gmail.com> wrote: 
Examples like those are your best bet here. It almost convinced me that it's a good idea.

Well that's a good sign, at least :) 

Matt Calabrese

unread,
Oct 13, 2015, 2:26:00 PM10/13/15
to ISO C++ Standard - Future Proposals
On Mon, Oct 5, 2015 at 11:43 AM, Matt Calabrese <cala...@google.com> wrote:
Following the "Allow values of void" discussion, I've put together a proposal[1] for updating void to be a Regular type.

Here are some drafts for library changes, assuming the Regular Void proposal is considered viable and worthwhile:

Remove Future-Related Explicit Specializations for Void: https://googledrive.com/host/0B66ERk4C7uoQSXRienRLZDNnaUE
Standard Input and Output Support for Void: https://googledrive.com/host/0B66ERk4C7uoQT01OLXVVSVF4UFE
Reply all
Reply to author
Forward
0 new messages