Allow values of void

669 views
Skip to first unread message

martin...@gmx.de

unread,
Jul 30, 2015, 4:49:44 PM7/30/15
to ISO C++ Standard - Future Proposals
Currently, declaring a variable as void is forbidden. That makes sense but restricts some cases of generic programming.

The intent is, to allow variables of type void but make them carry no actual data.

Consider:

template<typename R, typename Q, typename... A>
Q concat
(R (*fn)(A...), Q (*fn2)(R), A&&... a)
{
  R result
= fn(a...);
 
return fn2(result);
}

int convert(float f)
{
 
return f;
}

void print(int a)
{
  std
::cout << a;
}

auto convert_and_print = std::bind(concat<int, void, float>, convert, print, std::placeholders::_1);
convert_and_print
(3.5f);

This method can be used, combined with bind to combine two methods into one. But it fails, when the return type of the first function is void. Why would we need that, well consider

void print(int a)
{
  std
::cout << a;
}
void printline()
{
  std
::cout << std::endl;
}
//auto print_with_ln = std::bind(concat<void, void, int>, print, printline, std::placeholders::_1);
// Fails horribly at the moment

I consider this a bad design by the language and thus propose the following:

A variable of type void
  • occupies no space (sizeof(void) == 0)
  • holds no value and can not be converted to any other type.
  • has an alignment of 1 (aligned to a storage unit boundary).
  • defines the following operators
    • defines no other operators but == and !=
    • compares equal to all other void variables (up to discussion)
  • is "swallowed" when given as an argument to a function. It appears as if it wasn't given (but the expression giving this void is evaluated).

Certainly, there is a second point we have to take care of.

A function with the signature R(A..., B...) also has the signature R(A..., void, B...) with typename R and typename...A, typename... B. This is to be able to bind no-args functions as a function accepting a void argument, and so forth.

With this the example print_with_ln would certainly compile and execute.

 -- WE

Thiago Macieira

unread,
Jul 30, 2015, 5:06:33 PM7/30/15
to std-pr...@isocpp.org
On Thursday 30 July 2015 13:49:44 martin...@gmx.de wrote:
> - is "swallowed" when given as an argument to a function. It appears as
> if it wasn't given (but the expression giving this void is evaluated).

This would suddenly allow:

void f(int, int);
void g();

int main()
{
f(1, g(), 1);
}

Did f get three arguments? No, it got two, because g() was void and got
swallowed.

The above could be done today with:

f(1, (g(), 1));

This suddenly also allows dereferencing a void* pointer.

template <typename T, T (*Func)()>
void h(T *ptr)
{
*ptr = Func();
}

Should the compiler warn that ptr wasn't used?
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358

martin...@gmx.de

unread,
Jul 30, 2015, 5:17:43 PM7/30/15
to ISO C++ Standard - Future Proposals, thi...@macieira.org

The above could be done today with:

        f(1, (g(), 1));
 
Note that there is a subtle difference: The way we can do it now puts a sequence point between the evaluation of g() and 1.

This suddenly also allows dereferencing a void* pointer.

template <typename T, T (*Func)()>
void h(T *ptr)
{
        *ptr = Func();
}

Should the compiler warn that ptr wasn't used?

Why is that a bad thing? I wouldn't even say that a warning was need, but you are right that one has to define the semantics for a void pointer. Possibly something like dereferencing a void pointer results in an (imaginary) reference to a void value. Asignment doesn't do anything at all, thus "it is safe" to do so.

If this was for the sake of generalizing generic programming methods, I would happily try to come up with good stuff.
 

Andrey Semashev

unread,
Jul 30, 2015, 5:23:25 PM7/30/15
to std-pr...@isocpp.org
On Thu, Jul 30, 2015 at 11:49 PM, <martin...@gmx.de> wrote:
> Currently, declaring a variable as void is forbidden. That makes sense but
> restricts some cases of generic programming.
>
> A variable of type void
>
> occupies no space (sizeof(void) == 0)

No (complete) type can have sizeof(T) == 0 because this would break
strict aliasing rules (i.e. no two objects can have the same memory
address). Void is incomplete, that's why you can't create values of it
or apply sizeof to it. You're asking to make it a complete type and as
such you will have to give it a size and a non-empty set of values
(even if that set consists of a single value).

I think this is a bad idea for multiple reasons. First, I don't think
the example you've given is motivating enough to make such drastic
changes to the language type system. Second, if changed this way, void
becomes no different from other types in that you have to return its
values from void functions, and that's a breaking change. You could go
further and make void yet special in that its value is implied in void
function returns, but that doesn't strike me as a sign of good design.
Third, void values will have to be actually passed around, and that
may break ABI as well. There are probably many more reasons that I'm
forgetting now.

What instead you could propose is to relax some rules on function call
syntax so that this is allowed:

void foo();
void bar();

void concat()
{
return foo(bar());
}

This doesn't require changes to the type system and just adds some
syntax uniformity.

Matt Calabrese

unread,
Jul 30, 2015, 6:06:08 PM7/30/15
to std-pr...@isocpp.org
On Thu, Jul 30, 2015 at 1:49 PM, <martin...@gmx.de> wrote:
Currently, declaring a variable as void is forbidden. That makes sense but restricts some cases of generic programming.

The intent is, to allow variables of type void but make them carry no actual data.

Yes!!! Working with void types are a pain in C++ in generic code and you are frequently forced to make needless specializations because of it. For examples, look at things like futures, expected, etc.

Someone with Sean Parent's email on hand should CC him, since he has expressed interested in exactly this notion and I'm sure he's given it a fair amount of thought already.

A variable of type void
  • occupies no space (sizeof(void) == 0)
This might be weird because of its implications with respect to the rest of the language as it currently is. For instance, can you make an array of them (you should logically be able to)? I think if we were starting from scratch with C++, size 0 would be a reasonable choice, but IIUC, it would be difficult to change now. That said, this doesn't rule out making instances of void types and using them in generic code.
  • defines the following operators
    • defines no other operators but == and !=
    • compares equal to all other void variables (up to discussion)
I see no reason to not support all of the comparisons. There's no reason to not support <, for instance. Also, void types should be assignable. They should be regular types.
  • is "swallowed" when given as an argument to a function. It appears as if it wasn't given (but the expression giving this void is evaluated).
I disagree here. You still should be able to pass void and have them really be separate arguments. Consider the following example in generic code:

//////////
template <class... Fun>
auto tuple_of_results(Fun... fun) {
  return std::make_tuple(fun()...);
}
//////////

Ideally, the above code should work for any return types, including void. If you strictly swallow void, this wouldn't work. Going further, to be truly proper, reference-to-void also should be supported (for instance, std::tie in generic code or std::forward_as_tuple, both of which are common).

Here's a somewhat wacky idea, but I'm just throwing it out there. Instead of "swallowing" void, allow passing nothing as an argument to a function whose parameter is void (and consider functions that take no parameters to be equivalent in usage, though maybe not in function type, to functions that take a single void parameter).

For example:

//////////
template <class T>
void foo(T arg) {}

int main() {
  foo<void>(); // This would work
  foo<void>(void{}); // This also works (default-construct a void)
  foo<void>(foo<void>()); // This also works
}
//////////

With respect to something like the definition of std::promise, it should allow for std::promise<void> to not require a specialization and instead just use the default template definition, and you could still use it the same way that you would today in non-generic code with the specialization that is specified (for instance, set_value() would just work).

In cases where "void" appears not as the sole parameter type, we can even extrapolate and do the same thing -- keep in mind that users still need to pass an argument, even if that argument is nothing:

//////////
// Notice that we can pass "nothing" here. This comes naturally from the above rule.
tuple<int, void, char, void, void> foo(5, , 'a', , );
//////////

This type of syntax actually has precedent in C++ already, since this is how you pass empty tokens to macros in C and C++.

Ultimately, if we are to have properly designed void types, they should not be particularly special. They should just be regular types like most other (proper) C++.

Matt Calabrese

unread,
Jul 30, 2015, 6:40:12 PM7/30/15
to std-pr...@isocpp.org
On Thu, Jul 30, 2015 at 3:06 PM, Matt Calabrese <cala...@google.com> wrote:
(and consider functions that take no parameters to be equivalent in usage, though maybe not in function type, to functions that take a single void parameter).

To avoid breaking changes, for functions declared using "void" as the parameter type directly (I.E. where void isn't a typedef or something dependent on a template parameter), the function type is a nullary function, as has always been the case. On the other hand, if the parameter type is "void" but that type came from a typedef, or is dependent on a template parameter (these are illegal in current C++), then the function type is unary, taking that void parameter. In this way, all existing function declarations would have the same exact type that they always had, but declaration of void functions in generic code would work in the way that is expected. If you want to make a unary function that takes a void parameter and you are in non-generic code, you can still do this by using a typedef of void (or an identity alias, passing in void).

Matt Calabrese

unread,
Jul 30, 2015, 6:56:49 PM7/30/15
to std-pr...@isocpp.org
On Thu, Jul 30, 2015 at 3:06 PM, Matt Calabrese <cala...@google.com> wrote:

In cases where "void" appears not as the sole parameter type, we can even extrapolate and do the same thing -- keep in mind that users still need to pass an argument, even if that argument is nothing:

//////////
// Notice that we can pass "nothing" here. This comes naturally from the above rule.
tuple<int, void, char, void, void> foo(5, , 'a', , );
//////////

This type of syntax actually has precedent in C++ already, since this is how you pass empty tokens to macros in C and C++.

A further thought -- if one really wanted to, this notion of a nothing argument doesn't have to be specific to void types at all. You could simply state that passing nothing is always shorthand for specifying default construction of a parameter. In that case, void is nothing special at all, but rather, it's simply a type that is default-constructible, which is why you can pass nothing as an argument for a unary void function. This just generalizes the idea of a nothing argument. In cases where the parameter type needs to be deduced to T and someone passes nothing, then T could just be specified to be deduced as void.

The only place this generalization of default-construct when passing nothing notion falls apart, that I can think of off-hand, is with parameters that are reference-to-void (such as set_value of a std::promise), since a reference isn't default constructible. set_value() worked prior to generalization because the nothing-ness was a shorthand for a default-constructed void. It's probably possible to generalize and have this work, but I'm not sure it's worth it. Just an interesting thought. 

Thiago Macieira

unread,
Jul 30, 2015, 7:49:37 PM7/30/15
to martin...@gmx.de, ISO C++ Standard - Future Proposals
On Thursday 30 July 2015 14:17:42 martin...@gmx.de wrote:
> This suddenly also allows dereferencing a void* pointer.
>
> template <typename T, T (*Func)()>
> void h(T *ptr)
> {
> *ptr = Func();
> }
>
> Should the compiler warn that ptr wasn't used?
>
> Why is that a bad thing? I wouldn't even say that a warning was need, but
> you are right that one has to define the semantics for a void pointer.
> Possibly something like dereferencing a void pointer results in an
> (imaginary) reference to a void value. Asignment doesn't do anything at
> all, thus "it is safe" to do so.
>
> If this was for the sake of generalizing generic programming methods, I
> would happily try to come up with good stuff.

It's like a pointer to nullptr_t: you don't read from it and you don't write
to it. It's always null.

martin...@gmx.de

unread,
Jul 30, 2015, 8:08:17 PM7/30/15
to ISO C++ Standard - Future Proposals, cala...@google.com

I see no reason to not support all of the comparisons. There's no reason to not support <, for instance. Also, void types should be assignable. They should be regular types.
 
Sounds reasonable. They should definitly be assignable, although assigning them should be effectively a no-op.
  • is "swallowed" when given as an argument to a function. It appears as if it wasn't given (but the expression giving this void is evaluated).
I disagree here. You still should be able to pass void and have them really be separate arguments. Consider the following example in generic code:

But then you'll break forward compatibility because foo() is different to foo(void), it's just another overload. That wouldn't be favorable, because it messes with old code.

> Ultimately, if we are to have properly designed void types, they should not be particularly special. They should just be regular types like most other (proper) C++.

I have to disagree. To have no problems with the current implementation and design, all operations directly regarding void have to be resolvable during compile time. If they wouldn't, that means that they'd carry some data, which is against the idea of this proposition.
So ultimatly, all operations with void-types, won't induce any kind of new undefined behaviour, because either the fault can be detected at compile-time or there is no trace of the operation left during runtime.


>It's like a pointer to nullptr_t: you don't read from it and you don't write
>to it. It's always null.

Yes to the first sentence, no to the second one. Because void carries no data, you can't read nor write anything. So one should be able to come up with a formulation that allows dereferencing a void*, but doesn't allow writing/reading from it (so it allows operations with a void object - no reading or writing necessary and also compile-time static)

Matt Calabrese

unread,
Jul 30, 2015, 8:39:41 PM7/30/15
to martin...@gmx.de, ISO C++ Standard - Future Proposals
On Thu, Jul 30, 2015 at 5:08 PM, <martin...@gmx.de> wrote:

I see no reason to not support all of the comparisons. There's no reason to not support <, for instance. Also, void types should be assignable. They should be regular types.
 
Sounds reasonable. They should definitly be assignable, although assigning them should be effectively a no-op.
  • is "swallowed" when given as an argument to a function. It appears as if it wasn't given (but the expression giving this void is evaluated).
I disagree here. You still should be able to pass void and have them really be separate arguments. Consider the following example in generic code:

But then you'll break forward compatibility because foo() is different to foo(void), it's just another overload. That wouldn't be favorable, because it messes with old code.

Not really. Regarding the differing function types, I address that in a later reply. Regarding if two overloads exist -- one where it's a nullary function and one where it's a unary void function, you could just state that the nullary function is a better match if you pass nothing whereas a unary function taking a void parameter is a better match if you actually pass in an expression of type void. There might be more to it than this, but I don't see a fundamental blocker here.
 
> Ultimately, if we are to have properly designed void types, they should not be particularly special. They should just be regular types like most other (proper) C++.

I have to disagree. To have no problems with the current implementation and design, all operations directly regarding void have to be resolvable during compile time. If they wouldn't, that means that they'd carry some data, which is against the idea of this proposition.

I'm not sure what you are disagreeing with. As far as I can tell we are in agreement. Can you clarify with an example of where you feel we disagree? A void type wouldn't carry any data at all as it would be logically stateless. A stateless type is still a regular type even though most of its operations are no-ops. The only thing a void instance needs is a unique address with respect to other voids if the address is ever observed, which is just a (sometimes unfortunate) implication when dealing with types in C++. All of the operations, such as copying and assigning are no-ops or yield compile-time constants (comparisons). All operations are also constexpr, etc. None of this goes against any of what you're proposing as far as I can see. The way to get it easy to use in generic code is to simply have it be a regular type, with the only differences being syntactic sugar and/or additional functionality.
 
So ultimatly, all operations with void-types, won't induce any kind of new undefined behaviour, because either the fault can be detected at compile-time or there is no trace of the operation left during runtime.

Can you give an example of what you mean? How would this introduce "new undefined behavior." There shouldn't be anything new here at all regarding undefined behavior, since void would just be like any other C++ type. There shouldn't be anything special about it at all. It should just be a stateless type.

Matt Calabrese

unread,
Jul 30, 2015, 8:54:13 PM7/30/15
to martin...@gmx.de, ISO C++ Standard - Future Proposals
On Thu, Jul 30, 2015 at 5:39 PM, Matt Calabrese <cala...@google.com> wrote:
Can you give an example of what you mean? How would this introduce "new undefined behavior." There shouldn't be anything new here at all regarding undefined behavior, since void would just be like any other C++ type. There shouldn't be anything special about it at all. It should just be a stateless type.

To be clear, this is (effectively) all that the complete void type would be, in my opinion:

//////////
struct void {};
constexpr bool operator ==(void, void) noexcept { return true; }
constexpr bool operator <=(void, void) noexcept { return true; }
constexpr bool operator >=(void, void) noexcept { return true; }
constexpr bool operator !=(void, void) noexcept { return false; }
constexpr bool operator <(void, void) noexcept { return false; }
constexpr bool operator >(void, void) noexcept { return false; }
//////////

And that is that. Everything else would just effectively syntactic sugar, such as an empty argument meaning "default-constructed-void". There shouldn't need to be very many changes to the language, and backwards compatibility can be maintained in the way I described earlier.

sean....@gmail.com

unread,
Jul 31, 2015, 12:50:09 AM7/31/15
to ISO C++ Standard - Future Proposals
Since my name was sucked into this - There are many ways the standard tries to avoid 0, I've considered giving a short talk titled "Nothing is Unavoidable". For every problem avoided, another is created. I'd like to see as many of these repaired as possible. I totally understand that many of these would break backward compatibility in a number of ways - but making void model Regular would make writing generic code _so_ much nicer.

empty classes and structs should have sizeof(0) to avoid inefficiencies and compressed pair hacks.
f(void) and f() are two different signatures - one takes a single argument of type void, the other takes no arguments.
void* should be a pointer to nothing, not a pointer to anything.
int a[0]; Should be perfectly legal. As should void a[42];
Aliasing rules should only apply to objects with size. Two objects of zero size can share the same address.
void x; should be legal, as should void x = f() when x returns void.

Babylonians "discovered" zero some 2400 years ago - but it took nearly 2000 years to find it's way into common use. Zero makes us uncomfortable because it is a discontinuity, but it should be dealt with directly instead of trying to hide or avoid it.

Jared Grubb

unread,
Jul 31, 2015, 1:17:00 AM7/31/15
to ISO C++ Standard - Future Proposals, martin...@gmx.de
I actually had a wish for this a while back. I had code that was doing something like JSON visitation:

    auto visit_string(VisitorType& visitor) {
       return visitor(stuff...);
    }

and then had another member like this:

    auto visit_array(VisitorType& visitor) {
       ++this->indentation;
       auto result = visitor(stuff...);
       --this->indentation;
       return result;
    }

The first one works with visitors that return 'void', but the second one does not compile -- even though it's conceptually the same thing.

I think others are making good arguments for and against but just wanted to chime in and express my support.

Jared

P.S. I did use an RAII-style thing in the end to work around this issue, but I would still prefer the above form.

Matt Calabrese

unread,
Jul 31, 2015, 2:04:48 AM7/31/15
to ISO C++ Standard - Future Proposals
On Thu, Jul 30, 2015 at 9:50 PM, <sean....@gmail.com> wrote:
Since my name was sucked into this

Thanks for replying, Sean!
 
- There are many ways the standard tries to avoid 0, I've considered giving a short talk titled "Nothing is Unavoidable". For every problem avoided, another is created. I'd like to see as many of these repaired as possible. I totally understand that many of these would break backward compatibility in a number of ways - but making void model Regular would make writing generic code _so_ much nicer.

Agreed, and please give that talk.

empty classes and structs should have sizeof(0) to avoid inefficiencies and compressed pair hacks.

I agree, though I think this is the only thing that would be considerably difficult to actually change in the language, as user-code can and likely does depend on objects of the same type having different addresses, sometimes in subtle ways that would be difficult to gleen. Even something as common and fundamental as using a pointer as an iterator into an array breaks down once you have empty types. There are unfortunately common assumptions and uses of addresses in existence that would break if we had size 0 objects. IMO, getting them to be size 0, specifically, just seems like a much harder thing to fix, and the gains aren't that much when compared to the benefits of simply having void be a regular type. 

f(void) and f() are two different signatures - one takes a single argument of type void, the other takes no arguments.

Ideally I want this, but it means that existing code that declares foo(void) now changes meaning. I know you favor not worrying about breaking code, but realistically this can have pretty far-reaching ramifications. In practice it's ABI breaking and things like void foo(); void foo(void) {} would no longer refer to the same functions. It shouldn't hurt generic code to just say that foo(void) is unary if and only if "void" is actually a typedef of void or dependent on a template parameter. As long as invocations of a nullary function and a function taking a single void parameter work the same way (perhaps only differing in which is a better match when passing in nothing), I think this has the same affect without being anymore difficult to write generic code.
 
void* should be a pointer to nothing, not a pointer to anything.

+1 though would this require doing anything at the language level other than removing implicit casts to void and requiring the explicit cast to be reinterpret_cast rather than static_cast? The language already allows converting pointers between alignment-compatible types, so unless that changes, too, this change seems not too tough. It's more of a change in how people should think about void* than how it's represented at the core language level.

int a[0]; Should be perfectly legal. As should void a[42];

+1 FWIW this at least already works for std::array and dynamically allocated arrays.
 
Aliasing rules should only apply to objects with size. Two objects of zero size can share the same address.

Again, I agree on principle, but I think the ramifications of this are probably too much, since existing types that are empty could all of a sudden have properties changed that could break code in really subtle ways. The only way I see something like this actually getting into the language is if when you declare a potentially-empty type, you'd do so in a way that explicitly permits the type to actually be size 0 and have modern aliasing rules. Of course, in new code you'd probably always want to declare every one of your types that way, so that sucks. This just seems like the hardest part of all of this to actually fix in the language. Everything else seems feasible and not even very controversial.

void x; should be legal, as should void x = f() when x returns void.

+1
 
Babylonians "discovered" zero some 2400 years ago - but it took nearly 2000 years to find it's way into common use. Zero makes us uncomfortable because it is a discontinuity, but it should be dealt with directly instead of trying to hide or avoid it.

Give your talk! I feel like most people who do generic programming should understand this, but it's not always immediately clear for some reason.

Matthew Woehlke

unread,
Jul 31, 2015, 10:04:31 AM7/31/15
to std-pr...@isocpp.org
On 2015-07-30 18:06, 'Matt Calabrese' via ISO C++ Standard - Future
Proposals wrote:
> Ultimately, if we are to have properly designed void types, they should not
> be particularly special. They should just be regular types like most other
> (proper) C++.

Food for thought:

Python has no concept of functions that do not have return values; a
function that does not explicitly return, or just says 'return', returns
None.

So this is legal:

def foo():
return

a = foo()
# 'a' has value 'None'

...but this is not:

def foo():
return

foo(None) # error, 'foo' takes 0 arguments, given 1
foo(foo()) # error, 'foo' takes 0 arguments, given 1

--
Matthew

Matt Calabrese

unread,
Jul 31, 2015, 2:41:37 PM7/31/15
to ISO C++ Standard - Future Proposals
On Fri, Jul 31, 2015 at 7:04 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
Food for thought:

Python has no concept of functions that do not have return values; a
function that does not explicitly return, or just says 'return', returns
None.

So this is legal:

  def foo():
    return

  a = foo()
  # 'a' has value 'None'

...but this is not:

  def foo():
    return

  foo(None) # error, 'foo' takes 0 arguments, given 1
  foo(foo()) # error, 'foo' takes 0 arguments, given 1

Yeah. I think if we were starting from scratch, we would want invoking a nullary function with the result of a function that returns void to be a compile error. Only a function that takes void as a parameter should be able to be passed an expression that result in void (no different from other types, void just happens to be a stateless type). The only reason we probably should allow invoking a nullary function with an expression that yields void is because it means code doesn't have to be rewritten in order for people to take advantage of this (for instance, code like the promise<void> specialization doesn't have to change in order for people to pass in the result of a function call returning void to set_value()). Then again, maybe that's not something to worry about and we just allow code to break here in order to force people to update their code (which usually means simplify their code). It also implies, imo, that the changes regarding void should impact library and not just core (we'd be more obligated to remove the promise<void> specialization, which may be a good thing anyway).

Matt Calabrese

unread,
Jul 31, 2015, 2:44:06 PM7/31/15
to ISO C++ Standard - Future Proposals
On Fri, Jul 31, 2015 at 11:41 AM, Matt Calabrese <cala...@google.com> wrote:
Then again, maybe that's not something to worry about and we just allow code to break here in order to force people to update their code (which usually means simplify their code).

I guess "break" is poor phrasing here. It's more just that they wouldn't be able to take advantage of passing void without making changes to their code. 

Arthur O'Dwyer

unread,
Aug 1, 2015, 2:40:08 PM8/1/15
to ISO C++ Standard - Future Proposals, martin...@gmx.de
On Thursday, July 30, 2015 at 1:49:44 PM UTC-7, martin...@gmx.de wrote:
Currently, declaring a variable as void is forbidden. That makes sense but restricts some cases of generic programming.

The intent is, to allow variables of type void but make them carry no actual data.

I've wanted this idea for a while, but I think you need to scale back the scope of it to just what's actually needed today for generic programming.
For example, there's no reason to support sizeof(void), because generic code doesn't use that construct — or if it does, it's already doing crazy things with memory allocation. (Also, there are 100% excellent reasons to keep sizeof(void) a SFINAE failure. Suddenly defining it would break a lot of existing code.)

Things I've wanted from void variables can be summed up as


    T f(); T g() { return f(); }  // should work even with T=void (and it does already today!)

    T f();  void g(T);  ... g(f());  // should work even with T=void

    struct s { T m; };  // should work even with T=void

    T f(); T v = f();  // should work even with T=void

In other words, we don't need any "operations on void" other than the ability to default-construct "void" variables; to initialize and assign to "void" variables (from expressions whose own type is "void"); and to pass a single void argument as the parameter to a function taking no arguments.

The ability to pass a single void argument to a function taking no arguments is, I think, the most palatable first step.
(Or second step, if we count void f() { return g(); } as the first step, already taken. A good proposal would do some historical research there into why that feature exists.)

There is no need to support "collapsing" void arguments (e.g. the ability to write f(((void)1, 2)) as f((void)1, 2)).
There is no need to support arithmetic, relational, or logical operations on void operands.
There is no need to define the size or alignment of void objects; being no-ops, they wouldn't participate in class layout anyway.



Consider:

template<typename R, typename Q, typename... A>
Q concat
(R (*fn)(A...), Q (*fn2)(R), A&&... a)
{
  R result
= fn(a...);
 
return fn2(result);
}
This method can be used, combined with bind to combine two methods into one. But it fails, when the return type of the first function is void. Why would we need that, well consider

Matt Calabrese

unread,
Aug 1, 2015, 6:54:59 PM8/1/15
to ISO C++ Standard - Future Proposals, Jesus Christ
On Sat, Aug 1, 2015 at 11:40 AM, Arthur O'Dwyer <arthur....@gmail.com> wrote:
there's no reason to support sizeof(void), because generic code doesn't use that construct — or if it does, it's already doing crazy things with memory allocation.

Sorry, but I have to call you out on that as it's simply not true. Even if you ignore the array case that was already mentioned, what about implementing something like a variant (assuming you aren't using a union internally, just for sake of the example)? Very commonly, you just make raw storage that has the maximum size of all of the possible types, and an alignment that satisfies the alignment requirements of all of the possible types. Great, now what if one of those types happens to be void? Now all of a sudden the code that retrieves the maximum size of all of the types would break as soon as it internally does sizeof(void). If you need more grounding for why this type of thing comes up in practice, consider the following (for the record, this is based on actual code that I've worked with very frequently in the past).

//////////
auto result = visit(function, some_variant);
//////////

In case it is not clear, here you are invoking function on a variant, dispatching accordingly based on what type is in the variant. Sometimes the return type of the invoked function differs depending on what was stored in the variant at run time, so the overall result can be represented as a discriminated union (another variant) of the results. If one of the return types happened to be void, then you have to work around it again.

We shouldn't start by saying "oh, you'd never have to do that with void in generic code, so it can differ from 'normal' types here." That is how we got into this mess to begin with. It's a flawed rationale right from the start. void IS just a type, like any other. Stop special casing it.
 
(Also, there are 100% excellent reasons to keep sizeof(void) a SFINAE failure. Suddenly defining it would break a lot of existing code.)

I exploit SFINAE a lot and that's a real stretch. Obviously this is necessarily a breaking change because of how easy it is to introspect C++, but in that sense there is pretty much nothing that's not a breaking change in the language. Give some practical, real world examples of where sizeof(void) being an error breaks "a lot of code" in a way that shouldn't have been written some other way to begin with.

There is no need to support "collapsing" void arguments

+1

There is no need to support arithmetic, relational, or logical operations on void operands.

No one is advocating arithmetic operations. We just want the type to be Regular. Comparisons always make sense. Let's stop trying to make void anything less than a regular type. Even if it's not immediately obvious to you why you'd want to use any one of the operations expected of regular types, you're going to simply be wrong as soon as you write any generic code involving that operation. A simple example of generic code where you'd want comparisons for void:

//////////
// Check that the results of function calls match
// when given different objects.
template<class T, class... Funs>
bool results_are_equal(T a, T b, Funs... funs)
{
  return    std::forward_as_tuple(funs(a)...)
         == std::forward_as_tuple(funs(b)...);
}
//////////

If one or more of the functions happens to return void, then that's perfectly fine. All instances of such a stateless type are equal. Just because you know at compile time that the types are equal doesn't mean that the operation itself is illogical.

The other comparison operators are just as sensible.
 
There is no need to define the size or alignment of void objects; being no-ops, they wouldn't participate in class layout anyway.

Again, we are talking about generic code. If you make the operations an error rather than giving a sensible result (and there are perfectly sensible results here), then all you're doing if forcing people to work around that in generic code.

As far as "getting things in gradually by making only some operations valid," I don't think that is at all any easier than simply stating that void is a complete type equivalent to:

//////////
struct void {};
constexpr bool operator ==(void, void) noexcept { return true; }
// And define the other comparisons as I did in another reply
//////////

This is very simple, and because you're not describing void as still some special entity in the language, the rules of other types apply without doing anything special (I.E. sizeof and alignas work fine). This also effectively punts on the question of is sizeof(void) == 0, since it makes no explicit mention of an exact size requirement, just like we do with most other types in the language.

Thiago Macieira

unread,
Aug 1, 2015, 7:59:19 PM8/1/15
to std-pr...@isocpp.org
On Saturday 01 August 2015 15:54:54 'Matt Calabrese' via ISO C++ Standard -
Future Proposals wrote:
> Sorry, but I have to call you out on that as it's simply not true. Even if
> you ignore the array case that was already mentioned, what about
> implementing something like a variant (assuming you aren't using a union
> internally, just for sake of the example)? Very commonly, you just make raw
> storage that has the maximum size of all of the possible types, and an
> alignment that satisfies the alignment requirements of all of the possible
> types. Great, now what if one of those types happens to be void? Now all of
> a sudden the code that retrieves the maximum size of all of the types would
> break as soon as it internally does sizeof(void). If you need more
> grounding for why this type of thing comes up in practice, consider the
> following (for the record, this is based on actual code that I've worked
> with very frequently in the past).

The problem isn't that a variant class couldn't be made to work with void
under those circumstances.

The problem is that people are using void in SFINAE contexts (usually, sizeof)
and expecting it to fail. Suddenly making it succeed and yield zero may break
a lot of code, quite possibly silently.

Any new use we make of void should be in contexts that it couldn't have been
used before, not even in SFINAE.

So while I'd like to do:

template <typename T> void f(T *ptr, T (*fptr)())
{
*ptr = fptr();
}

work without specialisation for f<void> (real use-case, see Qt's
qobjectdefs_impl.h), we can probably not do:

template <typename T> void f(T value, void (*fptr)(T));

because some poor-man's enable_if may have been using expansion to a void
parameter.

Matt Calabrese

unread,
Aug 2, 2015, 9:12:32 PM8/2/15
to ISO C++ Standard - Future Proposals
On Sat, Aug 1, 2015 at 4:59 PM, Thiago Macieira <thi...@macieira.org> wrote:
The problem is that people are using void in SFINAE contexts (usually, sizeof)
and expecting it to fail. Suddenly making it succeed and yield zero may break
a lot of code, quite possibly silently.

Again, I understand that this is the claim, but I doubt that it is common or something to be horribly concerned about. Places where sizeof is used for SFINAE exploitation generally have better alternatives. If you have actual real code that breaks because of this and it's common, not easily diagnosable, and not easily fixable, post it so that we are not talking in hypotheticals. Modern SFINAE exploitation usually does not rely on sizeof. SFINAE exploitation of expressions often uses decltype as opposed to sizeof, since sizeof can force premature instantiation of the definition of the operand and also because it can cause an undesirable substitution failure with respect to void already. If code is using expansion to a void parameter to force substitution failure, I doubt it is horribly common in user code (is there an easy way to figure this out?). Even if it were, sacrificing that seems very reasonable to me, given that there are more common alternatives regarding SFINAE exploitation that have been in use for quite a while. Making generic code easier to write correctly and removing the need for specializations like std::promise<void> seems much more important to the future of C++ than not breaking weird hacks that could have/should have be done in other ways anyway.

If we are to talk about SFINAE, there are very few things you can change in the language at all that wouldn't break some hypothetical SFINAE exploitation because of C++'s introspection capabilities. I don't think it makes sense to hold back progress because of that. The nature of void as an incomplete type isn't particularly unique in this regard.

Thiago Macieira

unread,
Aug 2, 2015, 10:30:44 PM8/2/15
to std-pr...@isocpp.org
On Sunday 02 August 2015 18:12:31 'Matt Calabrese' via ISO C++ Standard -
Future Proposals wrote:
> On Sat, Aug 1, 2015 at 4:59 PM, Thiago Macieira <thi...@macieira.org> wrote:
> > The problem is that people are using void in SFINAE contexts (usually,
> > sizeof)
> > and expecting it to fail. Suddenly making it succeed and yield zero may
> > break
> > a lot of code, quite possibly silently.
>
> Again, I understand that this is the claim, but I doubt that it is common
> or something to be horribly concerned about. Places where sizeof is used
> for SFINAE exploitation generally have better alternatives.

There being better alternatives does not imply the better alternatives were
used. Changing behaviour of existing code requires checking how much code
would be affected.

We're both saying that the number is non-zero. So the burden will fall on you
to prove that this non-zero number is small enough and easily fixed.

> If you have
> actual real code that breaks because of this and it's common, not easily
> diagnosable, and not easily fixable, post it so that we are not talking in
> hypotheticals. Modern SFINAE exploitation usually does not rely on sizeof.
> SFINAE exploitation of expressions often uses decltype as opposed to

decltype is a C++11 trick. Please look at the large codebase of pre-C++11
SFINAE.

> If we are to talk about SFINAE, there are very few things you can change in
> the language at all that wouldn't break some hypothetical SFINAE
> exploitation because of C++'s introspection capabilities. I don't think it
> makes sense to hold back progress because of that. The nature of void as an
> incomplete type isn't particularly unique in this regard.

There are few things that are syntactically valid but would produce an error
during template substitution. Changing those risks breaking SFINAE.

More often, when we change the language, we make code that wasn't
syntactically valid before become valid

Anyway, I'm not disagreeing with some uses of void. I'm simply agreeing with
Arthur that we should go very carefully and enable only what's really
necessary.

Matt Calabrese

unread,
Aug 2, 2015, 11:46:30 PM8/2/15
to ISO C++ Standard - Future Proposals
On Sun, Aug 2, 2015 at 7:30 PM, Thiago Macieira <thi...@macieira.org> wrote:
decltype is a C++11 trick. Please look at the large codebase of pre-C++11
SFINAE.

Expression SFINAE wan't actually explicitly in the standard prior to C++11, so sizeof on an expression yielding void back then was not portable to begin with, even regarding substitution failure. If the concern with respect to sizeof really is breakage of pre-C++11 code that was not portable even then, then I don't see how that is a worthwhile concern. We didn't have portable expression SFINAE until we also had decltype.

Anyway, by the time any of these changes would come in, C++11 will have been at a minimum of 6 years old (or longer, depending on if something like this would even be proposed by C++17). Are we really going to hold ourselves back because of that?

As far as possible usage of void as a means to cause substitution to fail by way of making a function have a void parameter, I find that more compelling, and yes, that can potentially cause some code to change in observable behavior. For that case, however, whether or not sizeof works for void has absolutely no bearing. That case would necessarily break since passing of void or reference-to-void is one of the more common things to come up in generic code that we'd like to fix (It's the primary reason we need a std::promise<void> specialization, for example, or see my tuple example, or, in the more abstract, any generic code where you need to invoke a function with an instance of a type that is dependent on a template parameter where that type may or may not be void). So sizeof or no sizeof, I think people would just have to accept that kind of change.

Getting back to whether sizeof would work or not -- if you don't allow sizeof to work on void, I find it hard to believe that you'd be able to do something as simple as create an array of them in the language without even more special casing. Even if you did special-cased that, you'd probably also need to special case pointer arithmetic on it. In short, if we only go partway toward making void a proper type, we'll just be trading some special casing for other special-casing further down the line, and that special casing still needs to be specified in the standard and understood by users of the language.

To restate, I do not advocate sizeof(void) == 0, I just advocate void being a complete type in the manner expressed earlier, which implies that sizeof and alignas work perfectly fine. sizeof anything being 0 in the language really does break a lot of assumptions in the language and would have implications on things as basic as arrays, etc. It would be great if we eventually got to allowing types to truly be empty, but that is not at all necessary to improve the state of generic programming in C++. The size of a type being allowed to be 0 is orthogonal and I don't think it's worth addressing in a proposal that simply makes void a regular type.

Anyway, I'm not disagreeing with some uses of void. I'm simply agreeing with
Arthur that we should go very carefully and enable only what's really
necessary.

My stance is that it's all really necessary if the goal truly is to not have to needlessly special-case void when writing generic code. The claims I'm refuting are that certain operations "don't make sense" for void. They do in fact make sense, just like they make sense for other types. I am also not at all convinced that bringing the proper changes in gradually will make the transition any easier, whether for users or for specification in the standard. The proper change (starting by making void a complete, Regular type, and the other minimal changes described in earlier replies) can be specified relatively easily, and because it doesn't rely on void being something particularly special in the language, the rules for "normal" types apply.

If people are genuinely concerned about breaking of some oddball SFINAE hacks (and I say this as someone who is very liberal with usage of SFINAE hacks), spreading out the changes only succeeds at delaying that breakage and delays us getting a proper solution. Ultimately, these changes should eventually be made if we want to end the special-casing of void in generic code, and so ultimately users would have to update such code regardless of whether the changes were made in C++17, or the next standard, or in some future standard after that.

Arthur O'Dwyer

unread,
Aug 3, 2015, 12:41:57 AM8/3/15
to std-pr...@isocpp.org
On Sun, Aug 2, 2015 at 8:46 PM, 'Matt Calabrese' via ISO C++ Standard - Future Proposals <std-pr...@isocpp.org> wrote:

As far as possible usage of void as a means to cause substitution to fail by way of making a function have a void parameter, I find that more compelling, and yes, that can potentially cause some code to change in observable behavior. For that case, however, whether or not sizeof works for void has absolutely no bearing. That case would necessarily break since passing of void or reference-to-void is one of the more common things to come up in generic code that we'd like to fix (It's the primary reason we need a std::promise<void> specialization, for example, or see my tuple example, or, in the more abstract, any generic code where you need to invoke a function with an instance of a type that is dependent on a template parameter where that type may or may not be void).

I think it's extremely debatable whether
    std::tuple<int, void, int>
or
    std::array<void, 3>
ought to be permitted by the language.

I'm personally interested in getting promise<void> and future<void> to work without special cases, but for those I don't think you need to evaluate sizeof(void) or do pointer arithmetic on void pointers or make arrays of void...  and you definitely don't need to evaluate void operands to operator==!  That's why I'm pushing back on this idea a bit: Let's see a concrete situation where the current state of void is causing problems, and see what the minimal fix would be for that situation.

For example, what if the following class template were proposed for standardization?

  template<class T> struct regularize {
    T t;
    T& get() { return t; }  // assume we also provide const, rvalue, etc. forms
    void set(T s) { t = s; }
  };
  template<class T> struct regularize<T&> {
    T *t;
    T& get() const { return *t; }
    void set(T& s) { t = &s; }
  };
  template<> struct regularize<void> {
    void get() const {}
    void set() {}
  };

Would proper use of regular<T> in place of raw T solve all the current use-cases?
Well, no, it wouldn't; but at least now we can focus on a smaller number of remaining problem cases.
I nominate

    template<class T> struct simple_future {
        regularize<T> rt;
        void set(T p) { rt.set(p); }
    };

Here we have a parameter of void type, which is currently disallowed.[*]
Once we allow that, we also have to allow the name of that parameter to denote an expression of void type.
And then we also need to allow calling a nullary function with a single parameter of void type.
...Or, something else needs to happen, which I merely haven't thought of yet.

–Arthur

[*] ...but only if the void type is template-dependent. If the void comes from a non-dependent typedef, it's accepted.
This might be intended to deal with the problem case of

    template<class T> void g(T) {}
    int main() {
        g();  // Should this call g<void>(void)?
        g(g(1));  // Should this?
     }

That is, even if we allow void-type arguments to functions, we might need to specify that void still can't be deduced as a parameter type; void arguments must appear only in non-deduced contexts.

Matt Calabrese

unread,
Aug 3, 2015, 2:02:46 AM8/3/15
to ISO C++ Standard - Future Proposals
On Sun, Aug 2, 2015 at 9:41 PM, Arthur O'Dwyer <arthur....@gmail.com> wrote:
On Sun, Aug 2, 2015 at 8:46 PM, 'Matt Calabrese' via ISO C++ Standard - Future Proposals <std-pr...@isocpp.org> wrote:

As far as possible usage of void as a means to cause substitution to fail by way of making a function have a void parameter, I find that more compelling, and yes, that can potentially cause some code to change in observable behavior. For that case, however, whether or not sizeof works for void has absolutely no bearing. That case would necessarily break since passing of void or reference-to-void is one of the more common things to come up in generic code that we'd like to fix (It's the primary reason we need a std::promise<void> specialization, for example, or see my tuple example, or, in the more abstract, any generic code where you need to invoke a function with an instance of a type that is dependent on a template parameter where that type may or may not be void).

I think it's extremely debatable whether
    std::tuple<int, void, int>
or
    std::array<void, 3>
ought to be permitted by the language.

Why? Simply stating that they shouldn't be permitted without any rationale isn't convincing. void can simply be thought of as a stateless type. Treating it as something less than that is precisely the problem we have today. Sean's analogy with respect to the notion of 0 is really on point. Don't act like 0 is not a number. Just because void has no state doesn't mean that it's not a type. It's just a type with no state. People make empty types in C++ all of the time, and there is no reason to think of void as anything different. Pretending that void is special is the source of why generic code in C++ requires special casing when dealing with void. We should be fixing the problem rather than trying to put band-aids over some of the issues.

The way you make void easy to use in generic code is you simply make it a regular type with no state. 0 is a number.

I'm personally interested in getting promise<void> and future<void> to work without special cases, but for those I don't think you need to evaluate sizeof(void) or do pointer arithmetic on void pointers or make arrays of void...  and you definitely don't need to evaluate void operands to operator==!
 
... How would making operator== defined for void be a problem for you or for anyone? Why would you draw the line there even if you didn't understand my examples of where it comes up in generic code? I am telling you flat out, with examples, where equality can come up in generic code (really all object types should be regular, but we can leave that for different thread...). There is nothing special about an instantiable void that means that == should not work. Just because you know at compile time that two instances of a type are equal does not mean that it is illogical to compare them. It's perfectly fine and well defined and can come up in generic code. Why do we define equality for array<int, 0>? Do you feel that this should be a compile error, instead? It's just an empty type, after all. The reason it's important is because the size of the array can be dependent and yet you still may need to logically compare two instances. Your code shouldn't break simply because you reached the 0 case. 0 is just a number. The same is true for void -- the result of a function may be a dependent type (including void) but you still may logically compare the result in your generic code. It's just a type without state. 0 is just a number.

For example, what if the following class template were proposed for standardization?

  template<class T> struct regularize {
    T t;
    T& get() { return t; }  // assume we also provide const, rvalue, etc. forms
    void set(T s) { t = s; }
  };
  template<class T> struct regularize<T&> {
    T *t;
    T& get() const { return *t; }
    void set(T& s) { t = &s; }
  };
  template<> struct regularize<void> {
    void get() const {}
    void set() {}
  };

Would proper use of regular<T> in place of raw T solve all the current use-cases?

No. I already gave a simple example of what that wouldn't cover -- a function that returns a tuple of results of function calls. I'm sorry that you do not accept that specifically as important, but it can and has come up in generic code, including actual code that I have written and that is used. If you haven't encountered situations such as these then you're just not writing generic code that deals with void when it is dependent and used with composition of other algorithms. Creating helper templates that try to push some of the special casing off does not accomplish the goal of actually removing the need for hacks and special casing, it just tries to standardize even more hacks. If we actually fix the language, we don't need these workarounds at all. 0 is just a number.
 
Well, no, it wouldn't; but at least now we can focus on a smaller number of remaining problem cases.
I nominate

    template<class T> struct simple_future {
        regularize<T> rt;
        void set(T p) { rt.set(p); }
    };

Don't you see what you are doing? You're just introducing more and more facilities to handle special casing rather than simply removing the need for the special casing. You are advocating that whenever people write generic code where void may come up, they need to go through a level of indirection though some standard template with specializations and overloads. Stop. This is the kind of stuff that we are already doing. We want to stop the need for specializations and extra indirection, and knowledge of tricks in order to write very simple generic code. We don't want to standardize more hacks. Fix the language. 0 is a number. void is a stateless type.

...Or, something else needs to happen, which I merely haven't thought of yet.

... Or you could make void a regular type (the definition of which is already smaller than your incomplete attempts at workarounds). This actually covers the cases, including the ones that we didn't enumerate in this thread, without adding any templates to the standard library that do not solve the underlying problem while still leaving generic code that deals with void as needlessly complicated to write. If we just make void regular, users can write generic code that [potentially] deals with void exactly as they'd write it now if they weren't covering the void "corner case." Stop treating void as something special. It's not.

And 0 is a number.

David Krauss

unread,
Aug 3, 2015, 2:09:56 AM8/3/15
to std-pr...@isocpp.org

On 2015–08–03, at 12:41 PM, Arthur O'Dwyer <arthur....@gmail.com> wrote:

I think it's extremely debatable whether
    std::tuple<int, void, int>
or
    std::array<void, 3>
ought to be permitted by the language.

I’m not following very closely, but note that std::tuple does have std::ignore which acts as a bit-bucket.

Perhaps it could be sufficient to map void to a bit-bucket type, as an alternative approach to regularize. Either way, you still have to deal with functions taking zero vs. one parameter.

    template<class T> struct simple_future {
        regularize<T> rt;
        void set(T p) { rt.set(p); }
    };

void set( std::tuple_element< std::tuple< T >, unpack_sequence( ! std::is_void_v< T > ) > const & ... p )
    { rt.set( p ... ); }

The pack p is length zero or one, depending on the Boolean result of is_void. The unpack_sequence operator is from a recent thread here; it creates an unexpanded pack of std::size_t values. It’s OK to mention type typename std::tuple< void > as long as it doesn’t get instantiated.

Ugly? Yes. Too clever? Maybe. Just mentioning it.

Paul Fultz II

unread,
Aug 3, 2015, 5:58:38 AM8/3/15
to ISO C++ Standard - Future Proposals


On Monday, August 3, 2015 at 1:02:46 AM UTC-5, Matt Calabrese wrote:
On Sun, Aug 2, 2015 at 9:41 PM, Arthur O'Dwyer <arthur....@gmail.com> wrote:
On Sun, Aug 2, 2015 at 8:46 PM, 'Matt Calabrese' via ISO C++ Standard - Future Proposals <std-pr...@isocpp.org> wrote:

As far as possible usage of void as a means to cause substitution to fail by way of making a function have a void parameter, I find that more compelling, and yes, that can potentially cause some code to change in observable behavior. For that case, however, whether or not sizeof works for void has absolutely no bearing. That case would necessarily break since passing of void or reference-to-void is one of the more common things to come up in generic code that we'd like to fix (It's the primary reason we need a std::promise<void> specialization, for example, or see my tuple example, or, in the more abstract, any generic code where you need to invoke a function with an instance of a type that is dependent on a template parameter where that type may or may not be void).

I think it's extremely debatable whether
    std::tuple<int, void, int>
or
    std::array<void, 3>
ought to be permitted by the language.

They should be permitted and they should be equivalent of `void`.
 

Why? Simply stating that they shouldn't be permitted without any rationale isn't convincing. void can simply be thought of as a stateless type. Treating it as something less than that is precisely the problem we have today. Sean's analogy with respect to the notion of 0 is really on point. Don't act like 0 is not a number. Just because void has no state doesn't mean that it's not a type. It's just a type with no state. People make empty types in C++ all of the time, and there is no reason to think of void as anything different. Pretending that void is special is the source of why generic code in C++ requires special casing when dealing with void. We should be fixing the problem rather than trying to put band-aids over some of the issues.

Ok, an empty type or a stateless type is a type that has a state of at least one, because the very existence of the type creates state. This is why creating a struct of two empty types has the same state as an empty type(ie `1 * 1 = 1`) or creating a variant of two empty types has the same state as a boolean(ie `1 + 1 = 2`). However, `void` is different, and so should be treated as a type with zero state. As such, using `void` as a member to a struct(or `tuple<int, void>`) should be equivalent to `void` because `x * 0 = 0`.

Magnus Fromreide

unread,
Aug 3, 2015, 7:35:58 AM8/3/15
to std-pr...@isocpp.org
On Mon, Aug 03, 2015 at 02:58:38AM -0700, Paul Fultz II wrote:
>
>
> On Monday, August 3, 2015 at 1:02:46 AM UTC-5, Matt Calabrese wrote:
> >
> > On Sun, Aug 2, 2015 at 9:41 PM, Arthur O'Dwyer <arthur....@gmail.com
> > <javascript:>> wrote:
> >
> >> On Sun, Aug 2, 2015 at 8:46 PM, 'Matt Calabrese' via ISO C++ Standard -
> >> Future Proposals <std-pr...@isocpp.org <javascript:>> wrote:
> >>
> >>>
> >>> As far as possible usage of void as a means to cause substitution to
> >>> fail by way of making a function have a void parameter, I find that more
> >>> compelling, and yes, that can potentially cause some code to change in
> >>> observable behavior. For that case, however, whether or not sizeof works
> >>> for void has absolutely no bearing. That case would necessarily break since
> >>> passing of void or reference-to-void is one of the more common things to
> >>> come up in generic code that we'd like to fix (It's the primary reason we
> >>> need a std::promise<void> specialization, for example, or see my tuple
> >>> example, or, in the more abstract, any generic code where you need to
> >>> invoke a function with an instance of a type that is dependent on a
> >>> template parameter where that type may or may not be void).
> >>>
> >>
> >> I think it's extremely debatable whether
> >> std::tuple<int, void, int>
> >> or
> >> std::array<void, 3>
> >> *ought* to be permitted by the language.
> >>
> >
> They should be permitted and they should be equivalent of `void`.
>
>
> >
> > Why? Simply stating that they shouldn't be permitted without any rationale
> > isn't convincing. void can simply be thought of as a stateless type.
> > Treating it as something *less* than that is precisely the problem we
> > have today. Sean's analogy with respect to the notion of 0 is really on
> > point. Don't act like 0 is not a number. Just because void has no state
> > doesn't mean that it's not a type. It's just a type with no state. People
> > make empty types in C++ all of the time, and there is no reason to think of
> > void as anything different. Pretending that void is special is the source
> > of why generic code in C++ requires special casing when dealing with void.
> > We should be fixing the problem rather than trying to put band-aids over
> > some of the issues.
> >
>
> Ok, an empty type or a stateless type is a type that has a state of at
> least one, because the very existence of the type creates state. This is
> why creating a struct of two empty types has the same state as an empty
> type(ie `1 * 1 = 1`) or creating a variant of two empty types has the same
> state as a boolean(ie `1 + 1 = 2`). However, `void` is different, and so
> should be treated as a type with zero state. As such, using `void` as a
> member to a struct(or `tuple<int, void>`) should be equivalent to `void`
> because `x * 0 = 0`.

How should incomplete types other than void, like for example

struct incomplete;

be handled under your proposal, should they get the same new features that you
are proposing for void, or should void be an odd exception?

/MF

Paul Fultz II

unread,
Aug 3, 2015, 10:39:34 AM8/3/15
to ISO C++ Standard - Future Proposals


How should incomplete types other than void, like for example

struct incomplete;

be handled under your proposal, should they get the same new features that you
are proposing for void, or should void be an odd exception?

Probably not. A union with an incomplete type member should be an incomplete type, because it is an yet-to-be-determined size, whereas if the union has a void member it should essentially be ignored.

 

/MF

Matt Calabrese

unread,
Aug 3, 2015, 1:40:04 PM8/3/15
to ISO C++ Standard - Future Proposals
On Mon, Aug 3, 2015 at 2:58 AM, Paul Fultz II <pful...@gmail.com> wrote:


On Monday, August 3, 2015 at 1:02:46 AM UTC-5, Matt Calabrese wrote:
On Sun, Aug 2, 2015 at 9:41 PM, Arthur O'Dwyer <arthur....@gmail.com> wrote:
On Sun, Aug 2, 2015 at 8:46 PM, 'Matt Calabrese' via ISO C++ Standard - Future Proposals <std-pr...@isocpp.org> wrote:

As far as possible usage of void as a means to cause substitution to fail by way of making a function have a void parameter, I find that more compelling, and yes, that can potentially cause some code to change in observable behavior. For that case, however, whether or not sizeof works for void has absolutely no bearing. That case would necessarily break since passing of void or reference-to-void is one of the more common things to come up in generic code that we'd like to fix (It's the primary reason we need a std::promise<void> specialization, for example, or see my tuple example, or, in the more abstract, any generic code where you need to invoke a function with an instance of a type that is dependent on a template parameter where that type may or may not be void).

I think it's extremely debatable whether
    std::tuple<int, void, int>
or
    std::array<void, 3>
ought to be permitted by the language.

They should be permitted and they should be equivalent of `void`.

Well, not exactly. They should definitely be permitted, but std::tuple<int, void, int> especially is not equivalent to void (the array one can be "equivalent" depending on our equivalence function, but I guess that's true for anything, though it certainly differs on type!). A set that contains one or more null sets is not, itself, a null set. I'm not approximating when I say that void should just be like a user-defined type that has no members and is regular. That really is all that it should be. You should expect tuple<int, void, int> to behave just like if you had tuple<int, some_user_defined_empty_type, int>. That's all it is, and that is what you expect it to be when you are writing generic code. You shouldn't try to reintroduce special casing. It's just a type that doesn't have members.

Ok, an empty type or a stateless type is a type that has a state of at least one, because the very existence of the type creates state. This is why creating a struct of two empty types has the same state as an empty type(ie `1 * 1 = 1`) or creating a variant of two empty types has the same state as a boolean(ie `1 + 1 = 2`). However, `void` is different, and so should be treated as a type with zero state. As such, using `void` as a member to a struct(or `tuple<int, void>`) should be equivalent to `void` because `x * 0 = 0`.

What? No. void is not special here. It's just like any user-defined type without members except it happens to be a built-in type. If you put an instance of your own empty type in a struct, that struct doesn't become empty. If you don't see why this hurts things in practice, again, just consider something as simple as a function that returns the result of N function calls as a tuple of size N. There is nothing logically wrong with this even if one of the functions doesn't have to return a type that contains state. You should be able to access any of results, iterate over the results, etc. Algorithms on regular types should simply work on it because there is absolutely no reason, other than manufactured reasons, for why they wouldn't. void is not special here. It's just an empty type. Representing it as something less than or different than object types only hurts things.

Matt Calabrese

unread,
Aug 3, 2015, 1:42:28 PM8/3/15
to ISO C++ Standard - Future Proposals
On Mon, Aug 3, 2015 at 4:35 AM, Magnus Fromreide <ma...@lysator.liu.se> wrote:
How should incomplete types other than void, like for example

struct incomplete;

be handled under your proposal, should they get the same new features that you
are proposing for void, or should void be an odd exception?

These changes come up because we'd be making void a complete type. It doesn't affect other incomplete types. User-defined types that are incomplete would remain incomplete.

Matt Calabrese

unread,
Aug 3, 2015, 1:45:04 PM8/3/15
to ISO C++ Standard - Future Proposals
On Mon, Aug 3, 2015 at 7:39 AM, Paul Fultz II <pful...@gmail.com> wrote:
Probably not. A union with an incomplete type member should be an incomplete type

We have no reason to change the behavior of unions containing incomplete types.
 
, because it is an yet-to-be-determined size, whereas if the union has a void member it should essentially be ignored.

A union with a void member shouldn't be ignored. It's a union with a void member. It is exactly like putting a user-defined type with no members into a union. There is nothing special here. There is nothing special about any of these cases. It's just, very simply, an empty type. It is not special.

Magnus Fromreide

unread,
Aug 3, 2015, 2:31:06 PM8/3/15
to 'Matt Calabrese' via ISO C++ Standard - Future Proposals
Would it make sense to only treat void as complete if the definition was
included, like other incomplete types?

void foo; // Error - void is incomplete

#include <void> // Contains compiler magic that "declares" the void type

void bar; // Ok (maybe, depending on the proposal)

The point of this is to allow old programs to continue working as they always
have.

/MF

Matt Calabrese

unread,
Aug 3, 2015, 2:55:13 PM8/3/15
to ISO C++ Standard - Future Proposals
Maybe, but this adds complexity to usage and also can cause subtle bugs. Worse is that it doesn't really solve the problem in the case where header files need to include <void>, which they definitely would since template definitions are what benefit from void being complete. An example of why this is a problem is, what happens if one header "needs" void to be incomplete (an old header that does something really weird with SFINAE), but a different header defines a template and expects void to be complete so that it can be properly used in generic code? The header with the template definition would include <void>, and if it just so happens to be included before the header that expects void to be incomplete, then the old, odd-ball SFINAE code could break in a subtle way! It will either stop compiling (good) or compile differently (bad, as you now have ODR violations in addition to incorrect run time code). A similar kind of breakage, but perhaps less of a problem, is if the person writing the template definition simply forgets to include <void>. There, the template definition then would usually break if someone uses it with void, however, what if, because of inclusion order, some other header file brought in <void>? Now that definition would work in that specific translation unit! Finally, what about the case where a template that wants complete void needs to invoke a template that expects incomplete void? Here the user is really out of luck. The same goes for interleaving of include files that expect void to be complete or void to be incomplete. It would be very difficult, and some times impossible, to get things to work in this environment. Instead, I think it makes much more sense to just allow some breakage here.

I want to restate, though, that I think people are really overestimating just what would break by making void complete. The standard SFINAE exploits do not at all rely on void being an incomplete type. We are being generous here and talking about hypothetical SFINAE exploits that have more accepted alternatives. Unfortunately, it is considerably difficult to put a number on if or how much actual real-world code would potentially break, because the things that would break are very oddball, one-off SFINAE tricks that you can't really search for. I think the only way to really get an idea of what could break is to implement complete, regular void in a compiler, and then go out and compile large projects to see what breaks.

Paul Fultz II

unread,
Aug 3, 2015, 4:38:43 PM8/3/15
to ISO C++ Standard - Future Proposals


On Monday, August 3, 2015 at 12:40:04 PM UTC-5, Matt Calabrese wrote:
On Mon, Aug 3, 2015 at 2:58 AM, Paul Fultz II <pful...@gmail.com> wrote:


On Monday, August 3, 2015 at 1:02:46 AM UTC-5, Matt Calabrese wrote:
On Sun, Aug 2, 2015 at 9:41 PM, Arthur O'Dwyer <arthur....@gmail.com> wrote:
On Sun, Aug 2, 2015 at 8:46 PM, 'Matt Calabrese' via ISO C++ Standard - Future Proposals <std-pr...@isocpp.org> wrote:

As far as possible usage of void as a means to cause substitution to fail by way of making a function have a void parameter, I find that more compelling, and yes, that can potentially cause some code to change in observable behavior. For that case, however, whether or not sizeof works for void has absolutely no bearing. That case would necessarily break since passing of void or reference-to-void is one of the more common things to come up in generic code that we'd like to fix (It's the primary reason we need a std::promise<void> specialization, for example, or see my tuple example, or, in the more abstract, any generic code where you need to invoke a function with an instance of a type that is dependent on a template parameter where that type may or may not be void).

I think it's extremely debatable whether
    std::tuple<int, void, int>
or
    std::array<void, 3>
ought to be permitted by the language.

They should be permitted and they should be equivalent of `void`.

Well, not exactly. They should definitely be permitted, but std::tuple<int, void, int> especially is not equivalent to void (the array one can be "equivalent" depending on our equivalence function, but I guess that's true for anything, though it certainly differs on type!). A set that contains one or more null sets is not, itself, a null set.

We aren't talking about sets. We are talking about states in a program(or typesystem rather). A `struct` or `tuple` is the product of the states of its members. If you define a struct with a member that has zero state, the resulting struct will have zero state as well.
 
I'm not approximating when I say that void should just be like a user-defined type that has no members and is regular. That really is all that it should be. You should expect tuple<int, void, int> to behave just like if you had tuple<int, some_user_defined_empty_type, int>. That's all it is, and that is what you expect it to be when you are writing generic code. You shouldn't try to reintroduce special casing. It's just a type that doesn't have members.

I thought the point was to embrace zero. Instead, you want to define `void` with one state rather than zero state. I don't think that is very sound at all.
 

Ok, an empty type or a stateless type is a type that has a state of at least one, because the very existence of the type creates state. This is why creating a struct of two empty types has the same state as an empty type(ie `1 * 1 = 1`) or creating a variant of two empty types has the same state as a boolean(ie `1 + 1 = 2`). However, `void` is different, and so should be treated as a type with zero state. As such, using `void` as a member to a struct(or `tuple<int, void>`) should be equivalent to `void` because `x * 0 = 0`.

What? No. void is not special here. It's just like any user-defined type without members except it happens to be a built-in type. If you put an instance of your own empty type in a struct, that struct doesn't become empty. If you don't see why this hurts things in practice, again, just consider something as simple as a function that returns the result of N function calls as a tuple of size N.

If one of the functions return `void` then the function should return `void`.
 
There is nothing logically wrong with this even if one of the functions doesn't have to return a type that contains state. You should be able to access any of results, iterate over the results, etc. Algorithms on regular types should simply work on it because there is absolutely no reason, other than manufactured reasons, for why they wouldn't. void is not special here. It's just an empty type. Representing it as something less than or different than object types only hurts things.


But by doing that, you are now injecting extra state into the program. Perhaps, that is acceptable for your use case, in which it would be better for the user to explicity transform`void` to an empty type, rather than silently inject extra state.

Zach Laine

unread,
Aug 3, 2015, 4:47:52 PM8/3/15
to std-pr...@isocpp.org
On Mon, Aug 3, 2015 at 3:38 PM, Paul Fultz II <pful...@gmail.com> wrote:

[snip]
 
What? No. void is not special here. It's just like any user-defined type without members except it happens to be a built-in type. If you put an instance of your own empty type in a struct, that struct doesn't become empty. If you don't see why this hurts things in practice, again, just consider something as simple as a function that returns the result of N function calls as a tuple of size N.

If one of the functions return `void` then the function should return `void`.
 
There is nothing logically wrong with this even if one of the functions doesn't have to return a type that contains state. You should be able to access any of results, iterate over the results, etc. Algorithms on regular types should simply work on it because there is absolutely no reason, other than manufactured reasons, for why they wouldn't. void is not special here. It's just an empty type. Representing it as something less than or different than object types only hurts things.


But by doing that, you are now injecting extra state into the program. Perhaps, that is acceptable for your use case, in which it would be better for the user to explicity transform`void` to an empty type, rather than silently inject extra state. 

What extra state is injected?  If I return a byte-sized void with an undefined value that should be ignored, but you choose not to ignore it, what can you do with it?  You can't read its value -- it's empty.  What is the outward change to your program that I will observe?

Zach

Matt Calabrese

unread,
Aug 3, 2015, 4:51:38 PM8/3/15
to ISO C++ Standard - Future Proposals
On Mon, Aug 3, 2015 at 1:38 PM, Paul Fultz II <pful...@gmail.com> wrote:


On Monday, August 3, 2015 at 12:40:04 PM UTC-5, Matt Calabrese wrote:
On Mon, Aug 3, 2015 at 2:58 AM, Paul Fultz II <pful...@gmail.com> wrote:


On Monday, August 3, 2015 at 1:02:46 AM UTC-5, Matt Calabrese wrote:
On Sun, Aug 2, 2015 at 9:41 PM, Arthur O'Dwyer <arthur....@gmail.com> wrote:
On Sun, Aug 2, 2015 at 8:46 PM, 'Matt Calabrese' via ISO C++ Standard - Future Proposals <std-pr...@isocpp.org> wrote:

As far as possible usage of void as a means to cause substitution to fail by way of making a function have a void parameter, I find that more compelling, and yes, that can potentially cause some code to change in observable behavior. For that case, however, whether or not sizeof works for void has absolutely no bearing. That case would necessarily break since passing of void or reference-to-void is one of the more common things to come up in generic code that we'd like to fix (It's the primary reason we need a std::promise<void> specialization, for example, or see my tuple example, or, in the more abstract, any generic code where you need to invoke a function with an instance of a type that is dependent on a template parameter where that type may or may not be void).

I think it's extremely debatable whether
    std::tuple<int, void, int>
or
    std::array<void, 3>
ought to be permitted by the language.

They should be permitted and they should be equivalent of `void`.

Well, not exactly. They should definitely be permitted, but std::tuple<int, void, int> especially is not equivalent to void (the array one can be "equivalent" depending on our equivalence function, but I guess that's true for anything, though it certainly differs on type!). A set that contains one or more null sets is not, itself, a null set.

We aren't talking about sets.

It's a metaphor. We are talking about something, in this case a tuple, that contains members. A tuple that contains an int and an instance of a type that just so happens to have not datamembers, does not all of a sudden imply that the tuple is somehow void. That makes no sense. The tuple has two members. An int and a type that has no members. That is all. There is nothing special here.

I thought the point was to embrace zero. Instead, you want to define `void` with one state rather than zero state. I don't think that is very sound at all.

What are you talking about? If you want to understand why things are the way the are, you should think about it in comparison to types that have data. In other words, A type with 2 members, vs. a type with 1 member, vs. a type with 0 members (any user-defined type with no members, or in this case, void).
 
If one of the functions return `void` then the function should return `void`.

That doesn't make any sense and isn't consistent with the rest of the language. You want a tuple of the results. The first function returns an int, the second returns void, the third returns int. The result is a tuple<int, void, int>. Your logic for why the result should be "void" and not tuple<int, void, int>, where you can access each individual component, is completely flawed. All void is is an empty type.

But by doing that, you are now injecting extra state into the program. Perhaps, that is acceptable for your use case, in which it would be better for the user to explicity transform`void` to an empty type, rather than silently inject extra state.

What?

Paul Fultz II

unread,
Aug 3, 2015, 6:57:33 PM8/3/15
to ISO C++ Standard - Future Proposals

Well, for the case of a tuple it doesn't inject extra state(because `x * 1 = 1`). However, it can be observed for sum types. A type of boolean has two states. You can easily sum two empty states together(ie variant<empty1, empty2>) to achieve two states like a boolean, but you shouldn't be able to achieve this with `variant<emtpy, void>`. Here's why ,say for example you had a variant that represent the call back of two functions:

struct empty
{};

void f();
empty g();

variant<void, empty> foo(bool pick_f)
{
    if (pick_f) return f();
    else return g();
}

So when I call `foo` it will return the result of either `f()` or `g()`. However, the result of `f()` has no state, so therfore the visit to the result of foo should have just one overload:

variant<void, empty> v = foo(i);
v.visit([](empty) { std::cout << "I'm always 'empty'"; });


 

Zach

Paul Fultz II

unread,
Aug 3, 2015, 7:06:36 PM8/3/15
to ISO C++ Standard - Future Proposals


Well, for the case of a tuple it doesn't inject extra state(because `x * 1 = 1`).

I meant to write `x * 1 = x`, of course.
 

Edward Catmur

unread,
Aug 3, 2015, 7:17:04 PM8/3/15
to ISO C++ Standard - Future Proposals
I think the confusion here is over whether void should be more like the zero type or the unit type. Sean's mention of zero could be construed as a vote in favor of the former, but I think the rest of what he wrote indicates that he's in fact arguing for void as the unit type. Paul appears to be in favor of void as the zero type.

For what it's worth, I think we have enough unit types in C++ already, and don't see why we need one more. At the same time, void is clearly not the zero type - a function returning zero is a function that does not return.

Matt Calabrese

unread,
Aug 3, 2015, 7:36:26 PM8/3/15
to ISO C++ Standard - Future Proposals
On Mon, Aug 3, 2015 at 3:57 PM, Paul Fultz II <pful...@gmail.com> wrote:
Well, for the case of a tuple it doesn't inject extra state(because `x * 1 = 1`). However, it can be observed for sum types. A type of boolean has two states. You can easily sum two empty states together(ie variant<empty1, empty2>) to achieve two states like a boolean, but you shouldn't be able to achieve this with `variant<emtpy, void>`. Here's why ,say for example you had a variant that represent the call back of two functions:

struct empty
{};

void f();
empty g();

variant<void, empty> foo(bool pick_f)
{
    if (pick_f) return f();
    else return g();
}

So when I call `foo` it will return the result of either `f()` or `g()`. However, the result of `f()` has no state, so therfore the visit to the result of foo should have just one overload:

No it shouldn't, the visitor should handle the void state. There is no reason to treat void as special here and not doing so prevents you from using such constructs in real generic code (not to mention that it means variant has an arbitrary special case). void is just a type like your struct empty{}. It just happens to be a built-in type rather than a user-defined type. You are gaining nothing by trying to make it something else and you are only making it harder to properly use in generic code.

variant<void, empty> v = foo(i);
v.visit([](empty) { std::cout << "I'm always 'empty'"; });

Why do you make such a claim? You've explicitly stated that you can contain void here in the parameter list of variant, and so your visitor should handle it in the visitation. It's nothing special. If you didn't want to handle it, you simply wouldn't put it in the variant. Just to keep things grounded with more practical examples, let's say that your function "foo" is generic code living on a server and is reacting to a remote invocation request. It invokes "f" or it invokes "g" based on the information in the request (i.e. the function name and serialized arguments) and it locally returns the result so that the calling-code can send a message back to the client. All that the caller of "foo" does with the result is visit it with a generic lambda that serializes the result that is contained and sends it back to the client. If one of the functions happened to have a return type of void, then so what? The serialization part of your visitation might be a null op, or it might contain type info, or anything at all, but it still sends back a message so that the client knows the call went through. Everything just works because void is nothing special here, and there is nothing logically incorrect with doing this in the visitor. You're just sending back a reply with the result -- it just so happens that the type may be one with no members. If you arbitrarily special-case void for variant in such a way that it corresponds to a lack of a member, rather than an empty member, then your visitation doesn't handle the void case and your client never receives the reply. This isn't some strange, contrived case and you will find that this kind of thing comes up a lot when you are dealing with function results in generic code. This very scenario is strikingly similar to code I've written and is in use, and is not particularly unique. Right now, there is extra indirection to handle the void case, but it simply should not be necessary when you think about the problem in the abstract. The same is true for all generic code where you are dealing with a dependent type that may or may not be void and you need to pass along whatever state is there (which may be state that contains nothing, such as void). 

If you don't buy into such explanations and real-world examples, then exactly what code do you have in mind that benefits from the behavior that you suggest? I understand that you're defining what you feel it should do, which seems to be different from any other type in the language, and yet I see no practical rationale or real-world code to show why this would ever be desired. As far as I can see, it just prevents people from easily writing generic code and keeps people having to make strange workarounds for void.

Matt Calabrese

unread,
Aug 3, 2015, 7:41:56 PM8/3/15
to ISO C++ Standard - Future Proposals
On Mon, Aug 3, 2015 at 4:17 PM, Edward Catmur <e...@catmur.co.uk> wrote:
For what it's worth, I think we have enough unit types in C++ already, and don't see why we need one more.

For the reasons expressed. This is genuinely important for generic code. Dealing with void properly in generic code currently requires specialization. This is true even in the standard library. Making void a complete, regular type removes the need for special-casing and makes it easier to write generic code.

Arthur O'Dwyer

unread,
Aug 3, 2015, 10:55:12 PM8/3/15
to std-pr...@isocpp.org
For the record, I do completely sympathize with Matt's point of view here. C++'s special-case treatment of void does seem to me like a huge problem. I just prefer to solve the problem in as conservative and incremental a fashion as possible.

Our intuitions about what std::tuple<int,void> would mean, if it were legal, are also 100% in agreement. That is, I disagree that it necessarily should be legal, but if it were legal, I believe it would have to have Matt's desired semantics.

Matt, what would you do about template type deduction in your system?

    template<typename T> void g(T) { }
    int main() {
        g();  // valid or invalid?
        g((void)0);  // valid or invalid?
    }

–Arthur

Matt Calabrese

unread,
Aug 3, 2015, 11:24:21 PM8/3/15
to ISO C++ Standard - Future Proposals
On Mon, Aug 3, 2015 at 7:55 PM, Arthur O'Dwyer <arthur....@gmail.com> wrote:
Matt, what would you do about template type deduction in your system?

    template<typename T> void g(T) { }
    int main() {
        g();  // valid or invalid?
        g((void)0);  // valid or invalid?
    }

It depends. The most conservative approach is to make g() invalid, and g((void)0) valid, deducing T as void. The downside of this is that it means that calling set_value() on something like a promise<void> would require an explicit expression. What probably makes the most sense is to instead have an empty argument be allowed as syntactic sugar for default-constructed void. This allows easy use of unary void functions from both generic and non-generic code. In other words, you'd be able to invoke std::promise<void>'s set_value (which would now actually take const void& or void&&), by just calling set_value() without passing in an explicit argument. The subtlety here is if there is an overload that actually is nullary. In such a case, the unary-void overload should be considered and substitution should occur, but the nullary function would be considered a better match during overload resolution:

//////////
using Void = void;

void foo() {}  // foo0
void foo(Void) {} // foo1

void bar() {} // bar0
template<class T>
void bar(T) {} // bar1

void baz(Void) {}

int main()
{
  foo(); // calls foo0
  foo(void()); // calls foo1

  bar(); // calls bar0
  bar(void()); // calls bar1, deducing T as void

  baz(); // calls baz
}
//////////

This is less conservative and a little more "risky," but would allow us to remove the std::promise<void> specialization without breaking most user-code (unless, or course, the user code was relying on the signature of set_value, such as taking its address). Compiler-folks are probably best at understanding the implications of allowing empty meaning default-constructed-void obeying the rules specified above regarding substitution and overload resolution.

Matt Calabrese

unread,
Aug 3, 2015, 11:32:54 PM8/3/15
to ISO C++ Standard - Future Proposals
On Mon, Aug 3, 2015 at 8:24 PM, Matt Calabrese <cala...@google.com> wrote:
This is less conservative and a little more "risky," but would allow us to remove the std::promise<void> specialization without breaking most user-code (unless, or course, the user code was relying on the signature of set_value, such as taking its address). Compiler-folks are probably best at understanding the implications of allowing empty meaning default-constructed-void obeying the rules specified above regarding substitution and overload resolution.

There is a also a more conservative approach that doesn't involve specifying an empty argument to potentially be syntactic sugar for default-constructed void: when removing the std::promise specialization for void, give set_value a default-argument that is just default-constructed T. This makes set_value easy to use without adding in the empty-argument solution, but it means that set_value now also has a default argument. You could use SFINAE to make it only default for void, but that's unfortunate and would go back to the desire to special-case for void. Really it would be great if it "just worked."

Matt Calabrese

unread,
Aug 3, 2015, 11:38:07 PM8/3/15
to ISO C++ Standard - Future Proposals
On Mon, Aug 3, 2015 at 8:24 PM, Matt Calabrese <cala...@google.com> wrote:
On Mon, Aug 3, 2015 at 7:55 PM, Arthur O'Dwyer <arthur....@gmail.com> wrote:
Matt, what would you do about template type deduction in your system?

    template<typename T> void g(T) { }
    int main() {
        g();  // valid or invalid?
        g((void)0);  // valid or invalid?
    }

It depends. The most conservative approach is to make g() invalid, and g((void)0) valid, deducing T as void. The downside of this is that it means that calling set_value() on something like a promise<void> would require an explicit expression. What probably makes the most sense is to instead have an empty argument be allowed as syntactic sugar for default-constructed void. This allows easy use of unary void functions from both generic and non-generic code. In other words, you'd be able to invoke std::promise<void>'s set_value (which would now actually take const void& or void&&), by just calling set_value() without passing in an explicit argument. The subtlety here is if there is an overload that actually is nullary. In such a case, the unary-void overload should be considered and substitution should occur, but the nullary function would be considered a better match during overload resolution:]

Also, before being pointed out, yes, this has the potential to break code, which is why it is the less conservative solution.

Arthur O'Dwyer

unread,
Aug 4, 2015, 2:55:32 AM8/4/15
to std-pr...@isocpp.org
On Mon, Aug 3, 2015 at 8:24 PM, 'Matt Calabrese' via ISO C++ Standard - Future Proposals <std-pr...@isocpp.org> wrote:
On Mon, Aug 3, 2015 at 7:55 PM, Arthur O'Dwyer <arthur....@gmail.com> wrote:
Matt, what would you do about template type deduction in your system?

    template<typename T> void g(T) { }
    int main() {
        g();  // valid or invalid?
        g((void)0);  // valid or invalid?
    }

It depends. The most conservative approach is to make g() invalid, and g((void)0) valid, deducing T as void. The downside of this is that [it doesn't solve the problem we want to solve, namely std::promise<void>::set_value].
 
Notice that std::promise<void>::set_value(void) isn't exactly the same case as what I'm talking about. In the set_value case, we merely need to allow a parameter to have a type that is dependent yet void. C++ already allows void types for non-dependently-typed parameters, so this should be relatively simple. I'm talking about actually using type deduction to deduce void for a parameter of deduced type.

However, even before we get there, I'm seeing repeatedly that you and I disagree about the sensicality of a "unary function taking void".
In my world, that's impossible; a function whose argument list is equivalent to void is nullary by definition.
And this is a good thing, because I want std::promise<void>::set_value() to be a nullary function.

    using Void = void;
    // All three of these function signatures declare a nullary function.
    // IMO we must keep this working.
    void foo(Void);
    void foo(void);
    void foo();


using Void = void;

void foo() {}  // foo0
void foo(Void) {} // foo1

In my world this is a violation of the ODR; foo0 and foo1 are the same function.
 

void bar() {} // bar0
template<class T>
void bar(T) {} // bar1

void baz(Void) {}

int main()
{
  foo(); // calls foo0
  foo(void()); // calls foo1

  bar(); // calls bar0
  bar(void()); // calls bar1, deducing T as void

The question I was asking in the previous message is: If bar0 was not declared, would bar(); call bar1?
 
  baz(); // calls baz

Of course; this has always been the case.

–Arthur

Roland Bock

unread,
Aug 4, 2015, 3:32:27 AM8/4/15
to std-pr...@isocpp.org
On 2015-08-04 08:55, Arthur O'Dwyer wrote:
using Void = void;

void foo() {}  // foo0
void foo(Void) {} // foo1

In my world this is a violation of the ODR; foo0 and foo1 are the same function.
Being aware that this is currently the case I wonder if it is a good idea?

If we could have values of type void, then I doubt that the above makes sense?

How about binary functions?

void foo0();              // zero parameters
void foo1(void);          // still zero parameters (you say)
void foo2(void, void);    // ???

and how about

void foo2(int, void);     // ???
void foo2(void, int);     // ???

I would prefer all of them to be different, foo0 having zero parameters, foo1 having one parameters, foo2 having two parameters.


Seems to me that this ship has sailed though. There is simply too much code depending on foo0 being the same as foo1, I guess :-(


We could however introduce a new thing, e.g. std::none, that could be designed from afresh and have all the nice features we would like to see in void.

Regards,

Roland




Matt Calabrese

unread,
Aug 4, 2015, 3:44:23 AM8/4/15
to ISO C++ Standard - Future Proposals
On Mon, Aug 3, 2015 at 11:55 PM, Arthur O'Dwyer <arthur....@gmail.com> wrote: 
Notice that std::promise<void>::set_value(void) isn't exactly the same case as what I'm talking about. In the set_value case, we merely need to allow a parameter to have a type that is dependent yet void. C++ already allows void types for non-dependently-typed parameters, so this should be relatively simple. I'm talking about actually using type deduction to deduce void for a parameter of deduced type.

I know the case you are talking about -- I already explained how it'd be handled, I'm just trying to show all cases for clarity and to show its consistency with the non-deduced version. I don't believe that we should change one without changing the other. Ideally they are consistent with one another.

However, even before we get there, I'm seeing repeatedly that you and I disagree about the sensicality of a "unary function taking void".
In my world, that's impossible; a function whose argument list is equivalent to void is nullary by definition.

Then you are being inconsistent in what you think void means. You've already stated that you understand what tuple<int, void> is and it is consistent with what I've stated. This effectively implies that you accept unary void is a sensible construct. To be clear on why that is, simply imagine a make_tuple-like facility that takes the arguments by value. What do you think the signature of that function is for a tuple<int, int>? What do you think the signature is for tuple<int, void, int>? What about tuple<void>? What about tuple<>? Note that the last two cases are very different. The former is a unary function that takes void. The latter is a true, nullary function. They are in no way the same thing. Do not flatten unary void to nullary. They are distinct.
 
And this is a good thing, because I want std::promise<void>::set_value() to be a nullary function.

    using Void = void;
    // All three of these function signatures declare a nullary function.
    // IMO we must keep this working.
    void foo(Void);
    void foo(void);
    void foo();

My mistake -- I was thinking that declarations using a typedef of void were currently illegal, but that is only the case if the typedef was dependent. I agree that this particular syntax shouldn't change meaning, then. However, regardless of what the syntax is in the non-dependent context, there still needs to be a way to declare such a function. However it is to be declared, the behavior shown is what we should expect. It's just less than ideal that we can't use the natural syntax for it in the unary case (a function taking a single void parameter). When the type is dependent, which is the case we are trying to fix for generic code, this would work directly.

The question I was asking in the previous message is: If bar0 was not declared, would bar(); call bar1?

Yes, as I described, that overload would be considered and go through substitution. The nullary version is only picked because it is a better match. If the nullary version weren't there, then bar1 would be picked.
 
  baz(); // calls baz

Of course; this has always been the case.

Again, I'm just enumerating the relevant cases. Pretend this is the hypothetical unary void case (as opposed to the nullary case), however it would actually be declared.

--

---
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/.

Matt Calabrese

unread,
Aug 4, 2015, 4:02:34 AM8/4/15
to ISO C++ Standard - Future Proposals
On Tue, Aug 4, 2015 at 12:32 AM, Roland Bock <rb...@eudoxos.de> wrote:

How about binary functions?

void foo0();              // zero parameters
void foo1(void);          // still zero parameters (you say)
void foo2(void, void);    // ???

and how about

void foo2(int, void);     // ???
void foo2(void, int);     // ???

I would prefer all of them to be different, foo0 having zero parameters, foo1 having one parameters, foo2 having two parameters.

Yes!!! They are all logically different, we realistically just can't use the syntax for foo1.

We could however introduce a new thing, e.g. std::none, that could be designed from afresh and have all the nice features we would like to see in void

Unfortunately that wouldn't help generic code unless everyone just updated all of their code over night and nobody instantiated templates with void, which realistically wouldn't happen. void is going to stay so ideally we actually fix it in some way. IMO, we're really close to getting there. The only thing we don't have IMHO is a good syntax for representing a unary void function when void is not dependent. It's fine if there is an odd-ball syntax for it because the dependent case is the one that comes up in generic code. You wouldn't need to use the weird syntax there.

One possible solution -- we could have unary void functions declared as:

/////
void foo(explicit void) {}
/////

This is syntax that was illegal before, introduces no new keywords or types, and doesn't look too obscure. There are probably many alternatives and I'm not saying this is what it should be, we'd just need to pick one that makes the most sense. 

Arthur O'Dwyer

unread,
Aug 4, 2015, 4:05:32 AM8/4/15
to std-pr...@isocpp.org
On Tue, Aug 4, 2015 at 12:32 AM, Roland Bock <rb...@eudoxos.de> wrote:
On 2015-08-04 08:55, Arthur O'Dwyer wrote:
using Void = void;

void foo() {}  // foo0
void foo(Void) {} // foo1

In my world this is a violation of the ODR; foo0 and foo1 are the same function.
Being aware that this is currently the case I wonder if it is a good idea?

If we could have values of type void, then I doubt that the above makes sense?
[...] 
We could however introduce a new thing, e.g. std::none, that could be designed from afresh and have all the nice features we would like to see in void.

Sounds like std::nullptr_t is already that thing: it's new, it's unitary, it provides relational operators. If you just want something that behaves like Python's None, nullptr fits the bill exactly.

Another new-in-C++11, unitary, regular type in the standard library is std::tuple<> (that is, a tuple of zero elements).

But the problem is generic programming that needs to work with void parameter types (a.k.a. nullary functions) and void return types — not nullptr_t, not std::tuple<>, but literally and exactly void. Inventing new types almost certainly won't solve the problem, because the problem specifically concerns void.

–Arthur

Arthur O'Dwyer

unread,
Aug 4, 2015, 4:25:31 AM8/4/15
to std-pr...@isocpp.org
On Tue, Aug 4, 2015 at 12:44 AM, 'Matt Calabrese' via ISO C++ Standard - Future Proposals <std-pr...@isocpp.org> wrote:

On Mon, Aug 3, 2015 at 11:55 PM, Arthur O'Dwyer <arthur....@gmail.com> wrote: 
Notice that std::promise<void>::set_value(void) isn't exactly the same case as what I'm talking about. In the set_value case, we merely need to allow a parameter to have a type that is dependent yet void. C++ already allows void types for non-dependently-typed parameters, so this should be relatively simple. I'm talking about actually using type deduction to deduce void for a parameter of deduced type.

I know the case you are talking about -- I already explained how it'd be handled, I'm just trying to show all cases for clarity and to show its consistency with the non-deduced version. I don't believe that we should change one without changing the other. Ideally they are consistent with one another.

However, even before we get there, I'm seeing repeatedly that you and I disagree about the sensicality of a "unary function taking void".
In my world, that's impossible; a function whose argument list is equivalent to void is nullary by definition.

Then you are being inconsistent in what you think void means. You've already stated that you understand what tuple<int, void> is and it is consistent with what I've stated. This effectively implies that you accept unary void is a sensible construct.

Not really. It means I see what you mean by tuple<int,void> — you mean an object t such that std::tuple_size(t) returns 2std::get<0>(t) returns an int and std::get<1>(t) returns nothing. That much isn't controversial to me.
I understand that you want to be able to construct such an object via std::make_tuple(1, void()). I consider that part controversial.

 
To be clear on why that is, simply imagine a make_tuple-like facility that takes the arguments by value. What do you think the signature of that function is for a tuple<int, int>? What do you think the signature is for tuple<int, void, int>? What about tuple<void>? What about tuple<>? Note that the last two cases are very different. The former is a unary function that takes void. The latter is a true, nullary function. They are in no way the same thing. Do not flatten unary void to nullary. They are distinct.

I agree that std::tuple<void>, if it were allowed, would be a different type from std::tuple<>, in exactly the same way that boost::mpl::list<void> is a different type from boost::mpl::list<>.
However, int(*)(void) and int(*)() denote the same type; always have, always will, and IMO always should.
I don't know how to reconcile std::tuple<void> with make_tuple. I don't think that introducing a new "unary void" is the right way to do it. Instead of introducing "unary void", I would rather leave std::tuple<void> ill-formed, or even say that it is well-formed but that instances of it cannot be constructed via any kind of make_tuple.

–Arthur

Bengt Gustafsson

unread,
Aug 4, 2015, 5:00:41 AM8/4/15
to ISO C++ Standard - Future Proposals
How about just defining that void behaves like an empty struct? This avoids us having to discuss every particular case. There are still the potentials for code breakage with tricky SFINAE but I don't see how that can ever be avoided if we change the behaviour of void at all. Note: I don't mean that void should BE an empty struct in that you can inherit from it, but that it acts like one when used in code.

Matt Calabrese

unread,
Aug 4, 2015, 5:06:48 AM8/4/15
to ISO C++ Standard - Future Proposals
On Tue, Aug 4, 2015 at 1:25 AM, Arthur O'Dwyer <arthur....@gmail.com> wrote:
I understand that you want to be able to construct such an object via std::make_tuple(1, void()). I consider that part controversial.
Yes, you say it's "controversial" but you don't ever explain why you think it doesn't make sense. It makes perfect sense and I've even given real-world examples in this thread that directly involve make_tuple and void. I may be the most active person in this thread right now, but I am not at all unique in this -- the notion that void should be like any other type is understood by many already in the generic programming world. All you have stated is simply that you intuitively feel a certain function type is "nonsensical" because it contains void in it. I'm sorry, but unless you have an explanation for why you think that's the case, then I'm simply not going to give your personal intuition any credit at all. Actually address what about it you think is nonsensical if you want to have a serious discussion about this, and in doing so, explain how your view would affect the examples already presented in this thread in a positive way.

I don't know how to reconcile std::tuple<void> with make_tuple. I don't think that introducing a new "unary void" is the right way to do it. Instead of introducing "unary void", I would rather leave std::tuple<void> ill-formed, or even say that it is well-formed but that instances of it cannot be constructed via any kind of make_tuple.
That's the kind of special casing we are removing and there is nothing illogical with a single void parameter. What do you think you are accomplishing by having it illegal? How many examples of it being used in practice do I have to give (I've already given several)? I apologize that I'm getting really annoyed here and it's starting to show, but PLEASE actually back up your arguments or I'm going to eventually just stop taking your replies seriously.

In an attempt to actually make progress here, let's back up. What of the following do you disagree with and why? Please do take the "why" part seriously.

we are trying to remove the special casing that is necessary in generic code when a dependent type may be void.
void can be thought of as a complete, empty, regular type (a formulation of the change that we are proposing, critique this if you want).
tuple<int, int> is a sensible type that has an int member and another int member.
tuple<void, int> is a sensible type that has a void member and an int member (you've already agreed to this).
the construction of a tuple should be syntactically consistent whether it is a tuple<int, float> or tuple<double, char> such that it can easily be constructed when the types are dependent.
the construction of a tuple should be syntactically consistent whether it is a tuple<int, float> or tuple<int, void> such that it can easily be constructed when the types are dependent.
having make_tuple work with void arguments eliminates special-casing in generic code.
have make_tuple fail to work when one of the types is void forces users to special-case their code when an operand would be void.

Roland Bock

unread,
Aug 4, 2015, 5:12:25 AM8/4/15
to std-pr...@isocpp.org
On 2015-08-04 10:02, 'Matt Calabrese' via ISO C++ Standard - Future Proposals wrote:
On Tue, Aug 4, 2015 at 12:32 AM, Roland Bock <rb...@eudoxos.de> wrote:

How about binary functions?

void foo0();              // zero parameters
void foo1(void);          // still zero parameters (you say)
void foo2(void, void);    // ???

and how about

void foo2(int, void);     // ???
void foo2(void, int);     // ???

I would prefer all of them to be different, foo0 having zero parameters, foo1 having one parameters, foo2 having two parameters.

Yes!!! They are all logically different, we realistically just can't use the syntax for foo1.

We could however introduce a new thing, e.g. std::none, that could be designed from afresh and have all the nice features we would like to see in void

Unfortunately that wouldn't help generic code unless everyone just updated all of their code over night and nobody instantiated templates with void, which realistically wouldn't happen.

Right. Note to self: Do not write to this list too often, when suffering from the flu.


void is going to stay so ideally we actually fix it in some way. IMO, we're really close to getting there. The only thing we don't have IMHO is a good syntax for representing a unary void function when void is not dependent. It's fine if there is an odd-ball syntax for it because the dependent case is the one that comes up in generic code. You wouldn't need to use the weird syntax there.

One possible solution -- we could have unary void functions declared as:

/////
void foo(explicit void) {}
/////

Looks a bit weird, but sure looks like an option for solving the dilemma...


Matt Calabrese

unread,
Aug 4, 2015, 5:14:35 AM8/4/15
to ISO C++ Standard - Future Proposals
On Tue, Aug 4, 2015 at 2:00 AM, Bengt Gustafsson <bengt.gu...@beamways.com> wrote:
How about just defining that void behaves like an empty struct? This avoids us having to discuss every particular case. There are still the potentials for code breakage with tricky SFINAE but I don't see how that can ever be avoided if we change the behaviour of void at all.

Thank you, this is what I'm saying! I don't even really care about the syntactic sugar regarding construction if it's considered too magical or prone to more breakage. Just having void be equivalent to an empty struct that is regular is all that is necessary to have it work in generic code and eliminate special casing. I don't understand the hang up on this.

Johannes Schaub

unread,
Aug 4, 2015, 5:40:12 AM8/4/15
to std-pr...@isocpp.org

If we are already in oddballs mode, why not express the nondependent case by dependent types

template<typename T = void>
void f(typename void_t<T>::type);

Johannes Schaub

unread,
Aug 4, 2015, 5:46:22 AM8/4/15
to std-pr...@isocpp.org

Altermatively just give the void parameter a name. A named parameter shouldn't catch the special case of meaning an empty parameter list.

Edward Catmur

unread,
Aug 4, 2015, 5:50:26 AM8/4/15
to std-pr...@isocpp.org
Yes! Also, deprecate (void) meaning () outside extern "C" - there's no need for it, as C++ doesn't have unspecified parameter lists (even in C they're obsolescent: 6.11.6 "Future language directions - Function declarators").

--

---
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/05prNzycvYU/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.

Miro Knejp

unread,
Aug 4, 2015, 9:32:47 AM8/4/15
to std-pr...@isocpp.org

> On 04 Aug 2015, at 11:12 , Roland Bock <rb...@eudoxos.de> wrote:
>
>> One possible solution -- we could have unary void functions declared as:
>>
>> /////
>> void foo(explicit void) {}
>> /////
>>
> Looks a bit weird, but sure looks like an option for solving the dilemma...
>
Except it requires modifying the callee instead of the caller, so it still won’t work with any existing code. Plus, how many people would remember to put that explicit in there? It is a manual opt-in to make other peoples’ generic code work with a function.

All of this really looks to me like we’re trying to make “void” be equivalent to the unit types in functional languages. There’s a lot of experience with those, so maybe that is a point to start doing research for C++ applicability. Yes, we already have unit types and one can define their own, but the point is that “void” currently is *not* a unit type and that’s what causes generic code headaches.

I don’t see it happening to have foo() and foo(void) define two different functions due to backwards compatibility.

Assuming for a moment that void were a unit type then it should be possible to create instances of it and call foo(void()) and therefore also foo(T()) if T=void. Unfortunately that breaks existing expression SFINAE as decltype(foo(T())) can be used to filter out deductions with T=void. Whether that is the best way to do it is pretty much irrelevant unless someone can show that it isn’t used in practice.

I know the pain of special casing void but unfortunately the term “the ship has sailed” is a term that comes up in these forums depressingly often. So I don’t think that the syntax void() will become possible under the assumption that the foo(T()) SFINAE is in use. One would probably require some type trait that is specialized for void and returns a magical type that can be used to call unary functions.

template<class T> struct babel_fish { using type = T; }
template<> struct babel_fish<void> { using type = /* dark magic*/; }

foo(babel_fish<T>());

I just hope we come up with a language solution because this is yet another load of library boilerplate that would need repeating *everywhere*.

Paul Fultz II

unread,
Aug 4, 2015, 10:57:44 AM8/4/15
to ISO C++ Standard - Future Proposals

On Tuesday, August 4, 2015 at 4:00:41 AM UTC-5, Bengt Gustafsson wrote:
How about just defining that void behaves like an empty struct? This avoids us having to discuss every particular case. There are still the potentials for code breakage with tricky SFINAE but I don't see how that can ever be avoided if we change the behaviour of void at all. Note: I don't mean that void should BE an empty struct in that you can inherit from it, but that it acts like one when used in code.
 
But having void act like an empty struct, then it becomes a type with one value. However, `void` should mean a type with zero values(like a function that doesn't return anything). Of course, changing `void` from having zero values to one value could break a lot of assumptions made about code. I think its a bad idea. For some generic code, it may be safe to assume void as having one value instead of zero, but it would be better to use a library solution for this instead of baking in potential unsafe type assumptions into the language.

Jared Grubb

unread,
Aug 4, 2015, 11:04:57 AM8/4/15
to std-pr...@isocpp.org
On Aug 4, 2015, at 01:05, Arthur O'Dwyer <arthur....@gmail.com> wrote:

On Tue, Aug 4, 2015 at 12:32 AM, Roland Bock <rb...@eudoxos.de> wrote:
On 2015-08-04 08:55, Arthur O'Dwyer wrote:
using Void = void;

void foo() {}  // foo0
void foo(Void) {} // foo1

In my world this is a violation of the ODR; foo0 and foo1 are the same function.
Being aware that this is currently the case I wonder if it is a good idea?

If we could have values of type void, then I doubt that the above makes sense?
[...] 
We could however introduce a new thing, e.g. std::none, that could be designed from afresh and have all the nice features we would like to see in void.

Sounds like std::nullptr_t is already that thing: it's new, it's unitary, it provides relational operators. If you just want something that behaves like Python's None, nullptr fits the bill exactly.

The issue there is that is that nullptr is convertible to lots of things (T*) and not to a many others (T). That makes it an odd universal void type.


Another new-in-C++11, unitary, regular type in the standard library is std::tuple<> (that is, a tuple of zero elements).

But the problem is generic programming that needs to work with void parameter types (a.k.a. nullary functions) and void return types — not nullptr_t, not std::tuple<>, but literally and exactly void. Inventing new types almost certainly won't solve the problem, because the problem specifically concerns void.

Maybe there should be a new special type defined as decltype(void), which is convertible to and from places where ‘void’ currently exists, but is also a regular type. It can be a “conduit” type to make void things regular, but give you an escape hatch back to ‘void’ when it’s needed.

auto v = foo();   // type is ‘decltype(void)’
return v;            // ‘decltype(void)’ is convertible to void return type

We define a name under namespace std that is defined to be that type (like nullptr_t does).



–Arthur

--

---
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.

Zach Laine

unread,
Aug 4, 2015, 11:13:07 AM8/4/15
to std-pr...@isocpp.org
Again, I don't see the one value you mention.  Please point to the value below:

struct foo
{
};

Where is it?  Objects of type "foo" have an address, but no value, right?  What am I missing? 

Zach

Edward Catmur

unread,
Aug 4, 2015, 11:15:49 AM8/4/15
to std-pr...@isocpp.org
Here is the value:

    foo{}

Compare to [basic.fundamental]:

> [...] 9 - The void type has an empty set of values. [...]

An expression of type void *does not have a value*.

--

---
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/05prNzycvYU/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.

Thiago Macieira

unread,
Aug 4, 2015, 11:25:02 AM8/4/15
to std-pr...@isocpp.org
On Tuesday 04 August 2015 10:13:04 Zach Laine wrote:
> Again, I don't see the one value you mention. Please point to the value
> below:
>
> struct foo
> {
> };
>
> Where is it? Objects of type "foo" have an address, but no value, right?
> What am I missing?

It took me some time to understand Matt's proposition too... there's a very
subtle difference between types with zero states and types with one state.

An empty struct, just like std::nullptr_t, can only assume one state. You can
say the state is "exists" or "created". Compare this to bool, which has two
states and a tristate which has, as its name says, three states.

A zero-state type requires a jump, a leap of faith. Remember when you were
sometime in early school and discussed zero: how can you have zero of
something? I don't remember the details of the discussion, but I remember
having discussions about it because it's non-obvious, like negative numbers
aren't obvious other.

Having zero of something is the same as not having. So an object with zero
states is the same as the object not existing. It does not even have an
address.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358

Paul Fultz II

unread,
Aug 4, 2015, 11:32:14 AM8/4/15
to ISO C++ Standard - Future Proposals


Its `foo()`. Thats the one value. You can assign it to a variable:

auto x = foo();

So what value does `x` have? It has to have a value, and it is `foo()`. Futhermore, you can return its value from a function:

foo f()
{ return foo(); }

If `foo` didn't have a value, then we wouldn't be able to return it from a function. Also, you can observe that the values start to add up when we start to sum them using a variant. How many values does `foo` have now?

struct empty1 {};
struct empty2 {};

typedef variant<empty1, empty2> foo;

It has two: `empty1()` and `empty2()`.

 

Zach

Zach Laine

unread,
Aug 4, 2015, 11:36:06 AM8/4/15
to std-pr...@isocpp.org
On Tue, Aug 4, 2015 at 10:24 AM, Thiago Macieira <thi...@macieira.org> wrote:
On Tuesday 04 August 2015 10:13:04 Zach Laine wrote:
> Again, I don't see the one value you mention.  Please point to the value
> below:
>
> struct foo
> {
> };
>
> Where is it?  Objects of type "foo" have an address, but no value, right?
> What am I missing?

It took me some time to understand Matt's proposition too... there's a very
subtle difference between types with zero states and types with one state.

An empty struct, just like std::nullptr_t, can only assume one state. You can
say the state is "exists" or "created". Compare this to bool, which has two
states and a tristate which has, as its name says, three states.

A zero-state type requires a jump, a leap of faith. Remember when you were
sometime in early school and discussed zero: how can you have zero of
something? I don't remember the details of the discussion, but I remember
having discussions about it because it's non-obvious, like negative numbers
aren't obvious other.

Having zero of something is the same as not having. So an object with zero
states is the same as the object not existing. It does not even have an
address.

I'm a bit confused by your response.  IFAICT, Matt is suggesting that void behave as if it were defined as struct void{}.  Right?

Zach

Zach Laine

unread,
Aug 4, 2015, 11:41:08 AM8/4/15
to std-pr...@isocpp.org
Please write an if statement predicated on the values of empty1() and empty2() that takes different paths depending on runtime state, without relying on UB.

My point is that there is no non-UB-invoking observable runtime state for these objects.  They have no values that are meaningful to my program's state.  They are meaningful at compile time only.

Zach

Thiago Macieira

unread,
Aug 4, 2015, 11:41:53 AM8/4/15
to std-pr...@isocpp.org
On Tuesday 04 August 2015 10:36:03 Zach Laine wrote:
> I'm a bit confused by your response. IFAICT, Matt is suggesting that void
> behave as if it were defined as struct void{}. Right?

I thought he was, until the discussion on std::tuple<int, void, int>.

Paul Fultz II

unread,
Aug 4, 2015, 12:14:05 PM8/4/15
to ISO C++ Standard - Future Proposals

foo x = empty1();

if (x.which() == 0) std::cout << "empty1";
else std::cout << "empty2";

Thats not UB. As someone else suggested that `variant` have a templated `has` method, you could write this:

if (x.has<empty1>()) std::cout << "empty1";
else std::cout << "empty2";

Same logic, just easier to read.

 

My point is that there is no non-UB-invoking observable runtime state for these objects.  They have no values that are meaningful to my program's state.  They are meaningful at compile time only.
 
I am discussing runtime values,not compile time values. In fact, `foo` has the same runtime values that a boolean has, with the addition of being able to pattern match on its values. The fact that runtime values and compile time values are the same is because it is dependently-typed.
 

Zach

Johannes Schaub

unread,
Aug 4, 2015, 12:37:42 PM8/4/15
to std-pr...@isocpp.org


Am 04.08.2015 17:15 schrieb "'Edward Catmur' via ISO C++ Standard - Future Proposals" <std-pr...@isocpp.org>:
>
> Here is the value:
>
>     foo{}
>
> Compare to [basic.fundamental]:
>
> > [...] 9 - The void type has an empty set of values. [...]
>
> An expression of type void *does not have a value*.
>

While I agree, I must point out that there does not appear to be consensus (even officially) on the definition of the term "value". While the spec has a seemingly clear definition of it, with the introduction of the terms "value computation" that can result in an object or function identity for lvalues and in member function identity for rvalues, the term "value" has been used by some in manners meaning "result meaning of expression evaluation".

> 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.

Zach Laine

unread,
Aug 4, 2015, 12:54:35 PM8/4/15
to std-pr...@isocpp.org
On Tue, Aug 4, 2015 at 11:14 AM, Paul Fultz II <pful...@gmail.com> wrote:


On Tuesday, August 4, 2015 at 10:41:08 AM UTC-5, Zach Laine wrote:
On Tue, Aug 4, 2015 at 10:32 AM, Paul Fultz II <pful...@gmail.com> wrote:


Its `foo()`. Thats the one value. You can assign it to a variable:

auto x = foo();

So what value does `x` have? It has to have a value, and it is `foo()`. Futhermore, you can return its value from a function:

foo f()
{ return foo(); }

If `foo` didn't have a value, then we wouldn't be able to return it from a function. Also, you can observe that the values start to add up when we start to sum them using a variant. How many values does `foo` have now?

struct empty1 {};
struct empty2 {};

typedef variant<empty1, empty2> foo;

It has two: `empty1()` and `empty2()`.

Please write an if statement predicated on the values of empty1() and empty2() that takes different paths depending on runtime state, without relying on UB.

foo x = empty1();

if (x.which() == 0) std::cout << "empty1";

Stop right there.  You are using the value of the discriminator "x.which()", which is an int with a runtime value.  You never used the values of empty1() and empty2().

Zach

Miro Knejp

unread,
Aug 4, 2015, 1:34:29 PM8/4/15
to std-pr...@isocpp.org
On 04 Aug 2015, at 17:32 , Paul Fultz II <pful...@gmail.com> wrote:



On Tuesday, August 4, 2015 at 10:13:07 AM UTC-5, Zach Laine wrote:
On Tue, Aug 4, 2015 at 9:57 AM, Paul Fultz II <pful...@gmail.com> wrote:

On Tuesday, August 4, 2015 at 4:00:41 AM UTC-5, Bengt Gustafsson wrote:
How about just defining that void behaves like an empty struct? This avoids us having to discuss every particular case. There are still the potentials for code breakage with tricky SFINAE but I don't see how that can ever be avoided if we change the behaviour of void at all. Note: I don't mean that void should BE an empty struct in that you can inherit from it, but that it acts like one when used in code.
 
But having void act like an empty struct, then it becomes a type with one value. However, `void` should mean a type with zero values(like a function that doesn't return anything). Of course, changing `void` from having zero values to one value could break a lot of assumptions made about code. I think its a bad idea. For some generic code, it may be safe to assume void as having one value instead of zero, but it would be better to use a library solution for this instead of baking in potential unsafe type assumptions into the language.

Again, I don't see the one value you mention.  Please point to the value below:

struct foo
{
};

Where is it?  Objects of type "foo" have an address, but no value, right?  What am I missing? 


Its `foo()`. Thats the one value. You can assign it to a variable:

Which is the very definition of a unit type. Technically they are indistinguishable. What changes that in C++ is the fact that we can assign every object a memory address and therefore give every instance an *identity*. One cannot write a meaningful operator== that can distinguish two foo operands without taking their address, but at that point we’re comparing identities, not states/values.

@Zach:

Matt Calabrese

unread,
Aug 4, 2015, 1:59:16 PM8/4/15
to ISO C++ Standard - Future Proposals
On Tue, Aug 4, 2015 at 8:24 AM, Thiago Macieira <thi...@macieira.org> wrote:
On Tuesday 04 August 2015 10:13:04 Zach Laine wrote:
> Again, I don't see the one value you mention.  Please point to the value
> below:
>
> struct foo
> {
> };
>
> Where is it?  Objects of type "foo" have an address, but no value, right?
> What am I missing?

It took me some time to understand Matt's proposition too... there's a very
subtle difference between types with zero states and types with one state.

In case there is any confusion, I DO mean struct void {}; I defined in C++ the type in a couple of replies in a hope that it would eliminate all confusion. If "stateless" is the confusing part, for clarity, I'm referring to the boost definition of stateless -- the docs have always had a standard reference, though it now appears outdated. I didn't think there would be confusion using this term in this forum. Quoting the docs:

//////////
A stateless type is a type that has no storage and whose constructors and destructors are trivial. That means that is_stateless only inherits from true_type if the following expression is true:

::boost::has_trivial_constructor<T>::value
&& ::boost::has_trivial_copy<T>::value
&& ::boost::has_trivial_destructor<T>::value
&& ::boost::is_class<T>::value
&& ::boost::is_empty<T>::value
//////////

Thiago Macieira

unread,
Aug 4, 2015, 2:30:57 PM8/4/15
to std-pr...@isocpp.org
On Tuesday 04 August 2015 10:59:14 'Matt Calabrese' via ISO C++ Standard -
Future Proposals wrote:
> In case there is any confusion, I DO mean struct void {}; I defined in C++
> the type in a couple of replies in a hope that it would eliminate all
> confusion

You've failed in eliminating confusion.

Please choose one only:

a) struct void {};
b) zero states

Thiago Macieira

unread,
Aug 4, 2015, 2:32:07 PM8/4/15
to std-pr...@isocpp.org
On Tuesday 04 August 2015 11:54:32 Zach Laine wrote:
> Stop right there. You are using the value of the discriminator
> "x.which()", which is an int with a runtime value. You never used the
> values of empty1() and empty2().

There's no need to, because they can only assume one possible value.

It's like checking whether a value of type std::nullptr_t is null. It is, you
don't have to check.

Matt Calabrese

unread,
Aug 4, 2015, 2:36:05 PM8/4/15
to ISO C++ Standard - Future Proposals
On Tue, Aug 4, 2015 at 8:41 AM, Thiago Macieira <thi...@macieira.org> wrote:
On Tuesday 04 August 2015 10:36:03 Zach Laine wrote:
> I'm a bit confused by your response.  IFAICT, Matt is suggesting that void
> behave as if it were defined as struct void{}.  Right?

I thought he was, until the discussion on std::tuple<int, void, int>.

What did I say that caused the confusion there? I still mean struct void {}; (and the Regular overloads). Refer to the definition I gave at the start of all of this. tuple behavior I described should not go against that and if I specified otherwise somewhere then I was in error.

To be absolutely clear, std::tuple<int, void, int> is just a tuple with 3 elements: an int, a void, and an int. a make_tuple invocation takes 3 parameters -- an int, a void, and an int (technically references). You would invoke it, usually in generic code, just as anything else: make_tuple(int_fun(), void_fun(), int_fun()). I further suggest that we may want to consider supporting syntactic sugar for constructing void, which would be a whitespace argument, in an attempt to bridge the gap when people update functions to now take a single void parameter, though currently do not (promise's set_value(), for example). In other words, you could equivalently call make_tuple(int_fun(), , int_fun()). The reason why this is somewhat compelling is it means that you can allow the nullary and unary void case to be invoked the same way, and just avoid ambiguities by preferring the nullary overload as a better match if there are multiple candidates. This syntactic sugar is at least a little bit risky, because it has some chance of breaking old code (particularly due to the potential need for an overload to go through substitution that previously wan't even considered, and a non-SFINAE failure can occur during that substitution process, or simply templates may be prematurely instantiated, which can legitimately alter meaning and behavior of the program).

To explain why this might be something we'd want to at least consider, go back to std::promise<void>. If we remove the separate specialization and instead rely on the default definition, set_value() would now have one version that takes a void&& and one version that takes a const void&. However, in real-world code today, people who call set_value() of std::promise<void> do not pass anything other than the promise itself (as this). This means that if we simply removed the specialization, it would definitely break code. If we support the notion of "empty argument potentially means void()", then this wouldn't break. std::promise is one example here, but real-world generic code also has specializations like these. If we want to make minimal breakages in code, this might help, but it is actually a trade-off in potential changes and isn't strictly sound. The conservative approach would be to just force people to update their code, since it doesn't muck with overload resolution, but I'm just throwing the idea out in the open because it's at least somewhat compelling and the effect in practice is that less user code would need to be updated. If this is the part that was causing the confusion, then let me know if it's not more clear.

Matt Calabrese

unread,
Aug 4, 2015, 2:47:41 PM8/4/15
to ISO C++ Standard - Future Proposals
On Tue, Aug 4, 2015 at 11:30 AM, Thiago Macieira <thi...@macieira.org> wrote:
On Tuesday 04 August 2015 10:59:14 'Matt Calabrese' via ISO C++ Standard -
Future Proposals wrote:
> In case there is any confusion, I DO mean struct void {}; I defined in C++
> the type in a couple of replies in a hope that it would eliminate all
> confusion

You've failed in eliminating confusion. 

Please choose one only:

a) struct void {};
b) zero states

I think that did clarify, but to be very clear, it's more akin to (a) without actually being a user-defined type. I'm using actual type definitions and standard terms in replies so that we don't have to go into the semantics of this stuff. If there is some ambiguity in terms, prefer the c++ standard meaning.

So, restated, I absolutely do not mean that void should be something less than or different from a complete type in C++. It should just be equivalent to a type with no members that is Regular. This should not be a new kind of entity for the language with its own semantics, as that would not aid in writing generic code. It would mean more special casing. All that we need for void is for it to be a regular type.

Patrice Roy

unread,
Aug 4, 2015, 3:38:18 PM8/4/15
to std-pr...@isocpp.org
I think it's reasonable to claim that since we can do such things as

bool is_empty1(empty1) { return true; }
bool is_empty1(...) { return false; }

auto x = empty1{};
if (is_empty1(x))
{
   // ...
}

we can hold that a monostate type such as empty1 has a value.

I'm not sure where to go to with changes to the semantics of void, as it has far-ranging implications, but monostates and values seem reasonable to me.

--

Zach Laine

unread,
Aug 4, 2015, 4:58:36 PM8/4/15
to std-pr...@isocpp.org
On Tue, Aug 4, 2015 at 1:31 PM, Thiago Macieira <thi...@macieira.org> wrote:
On Tuesday 04 August 2015 11:54:32 Zach Laine wrote:
> Stop right there.  You are using the value of the discriminator
> "x.which()", which is an int with a runtime value.  You never used the
> values of empty1() and empty2().

There's no need to, because they can only assume one possible value.

It's like checking whether a value of type std::nullptr_t is null. It is, you
don't have to check.

That is precisely my point.  This discussion started with Paul's statement that use of the "struct void{}:" definition injects extra state into your program that was not there before.  I haven't yet seen it.

Zach 

Magnus Fromreide

unread,
Aug 4, 2015, 5:05:27 PM8/4/15
to 'Edward Catmur' via ISO C++ Standard - Future Proposals
On Tue, Aug 04, 2015 at 10:50:24AM +0100, 'Edward Catmur' via ISO C++ Standard - Future Proposals wrote:
> Yes! Also, deprecate (void) meaning () outside extern "C" - there's no need
> for it, as C++ doesn't have unspecified parameter lists (even in C they're
> obsolescent: 6.11.6 "Future language directions - Function declarators").

I would expect this to needlessly introduce compilation erros in quite a few
code bases and are strongly opposed to it in any near time, but for the
presumed C++4x it might be a reasonable idea...

/MF
> 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.

Edward Catmur

unread,
Aug 4, 2015, 6:30:48 PM8/4/15
to std-pr...@isocpp.org

Previously a variant<bool, void> could have two values: true or false. Now it can have three values: true, false or void{}. There's your extra state.

--

Thiago Macieira

unread,
Aug 4, 2015, 6:36:03 PM8/4/15
to std-pr...@isocpp.org
On Tuesday 04 August 2015 23:30:46 'Edward Catmur' via ISO C++ Standard -
Future Proposals wrote:
> Previously a variant<bool, void> could have two values: true or false. Now
> it can have three values: true, false or void{}. There's your extra state.

If the variant could never have assumed the void type as its state, then why
was void even allowed in the template parameter list in the first place?

Sounds like a design defect to me.

Paul Fultz II

unread,
Aug 4, 2015, 6:45:19 PM8/4/15
to ISO C++ Standard - Future Proposals

If `void` has zero values then `bool`, `variant<empty1, empty2>`, `variant<empty1, empty2, void>` all have same number of values. However, if we define `void` as `struct void {};` then `variant<empty1, empty2, void>` has the same number of values as tribool. Essentially, adding an extra state to the types. This is example is trivial and obvious, however, since the assumptions of `void` is that it has no values, it could subtly easily break other assumptions in the program.

Also, having a type represent zero values is really necessary for completeness, especially if we add variant to the language since `void` is the identity element for variant. Plus, the identity element would allow monadic computations over variant types, which could help with defining DSLs.
 

Zach 

Edward Catmur

unread,
Aug 4, 2015, 6:46:41 PM8/4/15
to std-pr...@isocpp.org

I'm not so sure - last I looked you could form tuple<int, void> despite the resulting type being non-instantiable i.e. nullary. Such things have their uses.

Matt Calabrese

unread,
Aug 4, 2015, 6:53:59 PM8/4/15
to ISO C++ Standard - Future Proposals
On Tue, Aug 4, 2015 at 3:35 PM, Thiago Macieira <thi...@macieira.org> wrote:
On Tuesday 04 August 2015 23:30:46 'Edward Catmur' via ISO C++ Standard -
Future Proposals wrote:
> Previously a variant<bool, void> could have two values: true or false. Now
> it can have three values: true, false or void{}. There's your extra state.

If the variant could never have assumed the void type as its state, then why
was void even allowed in the template parameter list in the first place?

Sounds like a design defect to me.

+1 Unless there is some generic case that people have in mind and haven't actually expressed, there is no explanation for why anyone would want this special behavior for void.

Unless people have a clear, practical rationale for this other kind of entity, I don't understand why it's even being discussed. What value would it supposedly provide? All I see is more special casing, no aid in generic programming, and no example of why you would want this in practice. If it's just because people are clinging to a preconceived notion of "true" emptiness, then I think we need to just accept that thinking of the void data type in such a manner simply is not what would solve any of the problems that we set out to solve. I'm not refuting that such a notion is valid, only that there is no reason for us to have void model this particular concept. Unless there is a practical example for why one would want that particular representation of void, it's silly to even continue down this path. In what cases would such a representation benefit our code? It certainly doesn't simplify generic code in any way that the struct void {}; does.

Zach Laine

unread,
Aug 4, 2015, 6:58:40 PM8/4/15
to std-pr...@isocpp.org
On Tue, Aug 4, 2015 at 5:30 PM, 'Edward Catmur' via ISO C++ Standard - Future Proposals <std-pr...@isocpp.org> wrote:

Previously a variant<bool, void> could have two values: true or false. Now it can have three values: true, false or void{}. There's your extra state.

I think previously a variant<bool, void> had 3 states: bool-true, bool-false, and void.  In other words, the discriminator could have been 0, indicating that the bool type was active, or 1, indicating that the void type was active.  There were two states (true and false) within the discriminator==0 case, and only one within the discriminator==1 case.  Right?

Zach

Edward Catmur

unread,
Aug 4, 2015, 7:08:15 PM8/4/15
to std-pr...@isocpp.org

If void is nullary (which it is at present, pretty much) then variant<bool, void> could only have a discriminant of 0; the void type could not be active because it is not possible to have a value of type void. Only if void values are allowed does it become possible for the discriminant to take a value of 1.

--

Matt Calabrese

unread,
Aug 4, 2015, 7:15:07 PM8/4/15
to ISO C++ Standard - Future Proposals
On Tue, Aug 4, 2015 at 3:45 PM, Paul Fultz II <pful...@gmail.com> wrote:
Also, having a type represent zero values is really necessary for completeness, especially if we add variant to the language since `void` is the identity element for variant. Plus, the identity element would allow monadic computations over variant types, which could help with defining DSLs.

I have no doubt that you are trying to make a legitimate point here, but please ground this with an example, and explain precisely why you feel it makes sense to reuse "void" as such a type. Again, I don't refute that such a type is a valid notion, only that it makes no sense for void to be that type as making void such a type does not solve the actual problems we are facing. The struct void {}; definition of void makes sensible generic code work perfectly fine without special-casing void, and I have yet to see even one practical example from you regarding how this definition of void would provide any tangible benefit. Write a simple code example showing the benefit and explains why "void" must be this type.

Thiago Macieira

unread,
Aug 4, 2015, 7:40:16 PM8/4/15
to std-pr...@isocpp.org
On Tuesday 04 August 2015 23:46:39 'Edward Catmur' via ISO C++ Standard -
Future Proposals wrote:
> I'm not so sure - last I looked you could form tuple<int, void> despite the
> resulting type being non-instantiable i.e. nullary. Such things have their
> uses.

That one is fine, which is a case that would change interpretation with void
becoming instantiable.

Paul Fultz II

unread,
Aug 4, 2015, 7:51:36 PM8/4/15
to ISO C++ Standard - Future Proposals


On Tuesday, August 4, 2015 at 6:15:07 PM UTC-5, Matt Calabrese wrote:
On Tue, Aug 4, 2015 at 3:45 PM, Paul Fultz II <pful...@gmail.com> wrote:
Also, having a type represent zero values is really necessary for completeness, especially if we add variant to the language since `void` is the identity element for variant. Plus, the identity element would allow monadic computations over variant types, which could help with defining DSLs.

I have no doubt that you are trying to make a legitimate point here, but please ground this with an example, and explain precisely why you feel it makes sense to reuse "void" as such a type. Again, I don't refute that such a type is a valid notion, only that it makes no sense for void to be that type as making void such a type does not solve the actual problems we are facing.

It does make sense for `void` to be that type because it represents both return type for a function that doesn't return anything and it represents the subtype of all types(such as in `void*`).
 
The struct void {}; definition of void makes sensible generic code work perfectly fine without special-casing void,

But there could be library solutions to help alleviate most of this, something like this:

struct unit {};
template<class T>
using avoid = typename std::conditional<(std::is_void<T>()), unit, T>::type;


and I have yet to see even one practical example from you regarding how this definition of void would provide any tangible benefit. Write a simple code example showing the benefit and explains why "void" must be this type.

Its not just the benefits. Changing the fundamental type of void to have a singular value could lead to subtle problems in assumptions made about code(starting with injecting extra state). As for the benefits of a having a type to represent zero state is a little beyond me, because I don't write DSLs, but having tuples and variants form categories would allow them to form an F-algebra(because I believe they are dual categories) which would enable evaluation of recursive tree stuctures in a non-recursive fashion.

Matt Calabrese

unread,
Aug 4, 2015, 7:51:46 PM8/4/15
to ISO C++ Standard - Future Proposals
On Tue, Aug 4, 2015 at 4:40 PM, Thiago Macieira <thi...@macieira.org> wrote:
On Tuesday 04 August 2015 23:46:39 'Edward Catmur' via ISO C++ Standard -
Future Proposals wrote:
> I'm not so sure - last I looked you could form tuple<int, void> despite the
> resulting type being non-instantiable i.e. nullary. Such things have their
> uses.

That one is fine, which is a case that would change interpretation with void
becoming instantiable.

This can hypothetically change interpretation in some vague sense, but what code does this break? If people were using such a declaration as a type list, then that doesn't at all break by this, for example. Even if they were using void as a special tag type, that's not strictly a breaking change, it could conceivably not be compatible with new possible uses of void, but even would be really strange.

Why are we focused on this? If there is an actual problem people are worried about in code, ground it with an example. This seems like a really big stretch.

Matt Calabrese

unread,
Aug 4, 2015, 8:28:14 PM8/4/15
to ISO C++ Standard - Future Proposals
On Tue, Aug 4, 2015 at 4:51 PM, Paul Fultz II <pful...@gmail.com> wrote:
On Tuesday, August 4, 2015 at 6:15:07 PM UTC-5, Matt Calabrese wrote:
On Tue, Aug 4, 2015 at 3:45 PM, Paul Fultz II <pful...@gmail.com> wrote:
Also, having a type represent zero values is really necessary for completeness, especially if we add variant to the language since `void` is the identity element for variant. Plus, the identity element would allow monadic computations over variant types, which could help with defining DSLs.

I have no doubt that you are trying to make a legitimate point here, but please ground this with an example, and explain precisely why you feel it makes sense to reuse "void" as such a type. Again, I don't refute that such a type is a valid notion, only that it makes no sense for void to be that type as making void such a type does not solve the actual problems we are facing.

It does make sense for `void` to be that type because it represents both return type for a function that doesn't return anything and it represents the subtype of all types(such as in `void*`).

You are missing what I am saying. I understand that you want void to mean that because it coincides with your personal intuitions of "void", but it has no actual benefit for specifically the c++ void type to be such a type. If you want such a type we can make one, but using void for it does not solve any of the problems that we are trying to solve that exist in actual code. The reason void makes sense as struct void {}; is because that notion coincides with how things like return values are used in generic code. Don't decide what you personally thing void should mean a priori based on intuition and have that direct you. Instead, figure out what definition benefits the language based on how it is actually used in real code. I've given a lot of actual examples and you still haven't produced any, but here is another:

//////////
// Invoke a function, logging the arguments and return value
template <class Log, class Fun, class... P>
auto invoke_and_log(Log& log, Fun&& fun, P&&... args) {
  auto my_log = log.template make_function_log<Fun(args...)>();
  my_log.log_arguments(args...);
  auto result = std::forward<Fun>(fun)(std::forward<P>(args)...);
  my_log.log_success(result);
  return result;
}
//////////

You can write this (untested, sorry if there are typos) and it will "work" in c++. It, however, will currently fail if fun returns void. Why do you feel that this is somehow beneficial? If we had an instantiable, regular void, this would work even if the function returned void. You're still logging a successful function invocation, and your personal serialization function for void might be a no-op, but so what? What do you think you are gaining by clinging to some preconceived notion of what void ought to be? Let usage tell us the definition that benefits the writing of code.

On Tue, Aug 4, 2015 at 4:51 PM, Paul Fultz II <pful...@gmail.com> wrote:
But there could be library solutions to help alleviate most of this, something like this:

struct unit {};
template<class T>
using avoid = typename std::conditional<(std::is_void<T>()), unit, T>::type;

We already have to do these kinds of hacks. We are trying to eliminate the need for them.

On Tue, Aug 4, 2015 at 4:51 PM, Paul Fultz II <pful...@gmail.com> wrote:
Its not just the benefits. Changing the fundamental type of void to have a singular value could lead to subtle problems in assumptions made about code(starting with injecting extra state).

Show. An. Example.

On Tue, Aug 4, 2015 at 4:51 PM, Paul Fultz II <pful...@gmail.com> wrote:
As for the benefits of a having a type to represent zero state is a little beyond me, because I don't write DSLs, but having tuples and variants form categories would allow them to form an F-algebra(because I believe they are dual categories) which would enable evaluation of recursive tree stuctures in a non-recursive fashion.

I do write EDSLs and I deal with variants all of the time, and I don't see the benefit. Maybe there is a theoretical benefit to having such a type and I just haven't encountered it, which is why I'd like an example, but it should also clarify why we need "void" specifically to be that type. Having void be equivalent to a regular struct with no members solves real problems when writing generic code, and I've shown many actual examples of that.

Roland Bock

unread,
Aug 5, 2015, 3:42:34 AM8/5/15
to std-pr...@isocpp.org
Ok, so that page says:

In C, C++, C#, and Java, void expresses the empty type. The unit type in C would be struct {}, but an empty struct is forbidden by the C language specification .


But if we go all Wikipedia, then also add this page here:

https://en.wikipedia.org/wiki/Bottom_type (aka empty type)
In type theory, a theory within mathematical logic, the bottom type is the type that has no values. It is also called the zero or empty type. The bottom type is sometimes confused with the so-called "void type", which is actually a unit type, albeit one with no defined operations.

So Wikipedia says:

  * void is the bottom type (no value)
  * void is a unit type (exactly one value)

Wikipedia contradicts itself, thus it cannot really be used to decide. ;-)

I guess that is where most of the confusion in this thread arises from. Some want to interpret void as the value-less bottom type, others want to interpret void as a unit type (exactly one value).

Thus, when making statement about how void should behave, you should state whether you are in the "bottom type" or in the "unit type" fraction.


As of now, I am a member of the "unit type" fraction: void does have a value which - for instance - says that a function has finished regularly, in contrast to not returning at all, throwing an exception, or aborting the program.


Cheers,

Roland

Miro Knejp

unread,
Aug 5, 2015, 5:58:31 AM8/5/15
to std-pr...@isocpp.org

On 05 Aug 2015, at 09:42 , Roland Bock <rb...@eudoxos.de> wrote:

Ok, so that page says:

In C, C++, C#, and Java, void expresses the empty type. The unit type in C would be struct {}, but an empty struct is forbidden by the C language specification .


But if we go all Wikipedia, then also add this page here:

https://en.wikipedia.org/wiki/Bottom_type (aka empty type)
In type theory, a theory within mathematical logic, the bottom type is the type that has no values. It is also called the zero or empty type. The bottom type is sometimes confused with the so-called "void type", which is actually a unit type, albeit one with no defined operations.

So Wikipedia says:

  * void is the bottom type (no value)
  * void is a unit type (exactly one value)

Wikipedia contradicts itself, thus it cannot really be used to decide. ;-)

I guess that is where most of the confusion in this thread arises from. Some want to interpret void as the value-less bottom type, others want to interpret void as a unit type (exactly one value).

Thus, when making statement about how void should behave, you should state whether you are in the "bottom type" or in the "unit type" fraction.


As of now, I am a member of the "unit type" fraction: void does have a value which - for instance - says that a function has finished regularly, in contrast to not returning at all, throwing an exception, or aborting the program.

Yay for contradictions. I’d say that void as it stands in C++ now is somewhere between the bottom and unit type. It is really neither of them:

- Functions marked with void do return to the caller but shouldn’t if void was the bottom type
- Functions marked with void or taking void do not take or return values, but they would take or return the singular unit value if void were a unit type

In a perfect world C++ would have a builtin unit type and a builtin bottom type (so everyone would use those by default instead of rolling their own), right now it really has neither. Considering the status quo void would be much more useful in practice as a unit type. The vast majority of functions do terminate so the need for a real bottom type is minuscule.

I don’t think you’d have to change the ABI to support void as a unit type. The compiler can simply pretend that it’s passing a unit value to nullary external functions and it can make a new unit value out of thin air for functions returning void. Right now you cannot have void in the middle of the argument list so there is no possibility in ABI breakage for existing functions. Once the void unit type is properly mangled new functions can have it anywhere in the argument list.

The problem I still see is that decltype(foo(T())) can already be used to filter out void in SFINAE so if it were to suddenly be well-formed for T=void that can change the behavior of existing code. Where is that damn ship sailing to anyways, someone make it come back.

Matthew Woehlke

unread,
Aug 5, 2015, 9:54:14 AM8/5/15
to std-pr...@isocpp.org
On 2015-08-04 19:15, 'Matt Calabrese' via ISO C++ Standard - Future
Proposals wrote:
> On Tue, Aug 4, 2015 at 3:45 PM, Paul Fultz II <pful...@gmail.com> wrote:
>> Also, having a type represent zero values is really necessary for
>> completeness, especially if we add variant to the language since `void` is
>> the identity element for variant.
>
> I have no doubt that you are trying to make a legitimate point here, but
> please ground this with an example, and explain precisely why you feel it
> makes sense to reuse "void" as such a type.

...because that's how it is *already* used?

I've been rather half following the discussion, albeit with some
concern. I'm certainly not against making it easier to use 'void' in
generic programming, however I think it's critical to keep in mind that
the first requirement be that the following program does not change in
meaning:

void foo();

int main()
{
foo();
return 0;
}

void foo(void)
{
return;
}

(Note in particular that making void a regular type - i.e. 'struct
void{}' - WILL BREAK THIS! NOT ACCEPTABLE!)

Rather (without getting into some of the more esoteric template
questions), it seems like the following should be made legal:

// allow void declaration; initialization is irrelevant
void x;

// allow void assignment; exactly equivalent to:
// (void)foo(); void y;
void y = foo();

// allow deduction from void
auto z = x;

// allow passing void declaration as argument to function
// taking zero arguments
foo(x);

// allow comparing void declarations (see also relational operators
// for Python's 'None')
assert(x == y);
assert(!(x != y));

// (optional) "allow" taking the address of a void declaration
auto p = &x;
assert(p == nullptr); // ...but 'x' has no storage

I would consider any proposal that doesn't provide at least the above a
waste of time, if not a step in the wrong direction. In particular, I'm
concerned that there doesn't seem to be enough discussion how to
preserve calling a function with argument list "(void)" with no
arguments, or calling a function taking no arguments with a single
argument of type 'void'. These are required to add benefit without
breaking existing code, and they necessitate continuing to handle 'void'
specially.

'void' is the zero-value type and SHOULD be handled specially! The
objective here is to also allow it to be used in generic programming
with less specialization.

On 2015-08-04 20:28, 'Matt Calabrese' via ISO C++ Standard - Future
Proposals wrote:
> // Invoke a function, logging the arguments and return value
> template <class Log, class Fun, class... P>
> auto invoke_and_log(Log& log, Fun&& fun, P&&... args) {
> auto my_log = log.template make_function_log<Fun(args...)>();
> my_log.log_arguments(args...);
> auto result = std::forward<Fun>(fun)(std::forward<P>(args)...);
> my_log.log_success(result);
> return result;
> }

This will work, with the above, if 'Log::log_success(void)' exists. It's
not necessary for this example to make 'void' a regular type.

> If we had an instantiable, regular void, this would work even if the
> function returned void.

"Instantiable" may or may not be correct terminology here. In the 'void'
case, 'result' names an entity, but no storage is ever associated with
it; basically, 'void x' is just telling the compiler to allow you to
write 'x' again somewhere a value is not actually needed, e.g. 'foo(x)'
for 'void foo()' or 'return x' in a void function.

--
Matthew

j4...@dropbox.com

unread,
Aug 5, 2015, 1:24:08 PM8/5/15
to ISO C++ Standard - Future Proposals
On Wednesday, August 5, 2015 at 2:58:31 AM UTC-7, Miro Knejp wrote:
Yay for contradictions. I’d say that void as it stands in C++ now is somewhere between the bottom and unit type. It is really neither of them:

- Functions marked with void do return to the caller but shouldn’t if void was the bottom type
- Functions marked with void or taking void do not take or return values, but they would take or return the singular unit value if void were a unit type

The behavior of pointer-to-void makes void seem a lot like bottom, but I think in general it's much closer to unit. "Does not return a value" is tricky phrasing, since in the type theory where 'bottom' and 'unit' come from, "does not return a value" means "does not return at all". And there are already some contexts in which void seems very unit-like:

void f() { return void(); } // this is already legal
void g() { return f(); } // so is this

As for parameters, I think that debate belongs with the tuple / multiple-return-value discussion. (You could even argue that 'f()' looks a lot like passing a 0-tuple to f, but that's stretching the analogy pretty far...)

Matthew Woehlke

unread,
Aug 5, 2015, 1:53:47 PM8/5/15
to std-pr...@isocpp.org
On 2015-08-05 13:24, j4...@dropbox.com wrote:
> You could even argue that 'f()' looks a lot like passing a 0-tuple to
> f, but that's stretching the analogy pretty far...

You mean like this?

$ python
>>> def foo(*args):
... print len(args)
...
>>> foo()
0

;-)

Or this?

$ cat arglist.cpp
#include <cstdio>

template <typename... Args>
void foo(Args const&... args)
{
printf("%s called with %d arguments\n",
__func__, sizeof...(Args));
}

int main()
{
foo();
return 0;
}

$ g++ -std=c++11 arglist.cpp && ./a.out
foo called with 0 arguments

--
Matthew

Matt Calabrese

unread,
Aug 5, 2015, 2:31:02 PM8/5/15
to ISO C++ Standard - Future Proposals
On Wed, Aug 5, 2015 at 6:54 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
On 2015-08-04 19:15, 'Matt Calabrese' via ISO C++ Standard - Future
Proposals wrote:
> On Tue, Aug 4, 2015 at 3:45 PM, Paul Fultz II <pful...@gmail.com> wrote:
>> Also, having a type represent zero values is really necessary for
>> completeness, especially if we add variant to the language since `void` is
>> the identity element for variant.
>
> I have no doubt that you are trying to make a legitimate point here, but
> please ground this with an example, and explain precisely why you feel it
> makes sense to reuse "void" as such a type.

...because that's how it is *already* used?

No, it's really not. Even in the language as it is today it is inconsistently used as a unit type. It's just incomplete. As someone already mentioned, a true lack of result would be more accurately represented by a function not returning at all (I.E. a [[noreturn]] function that terminates). That is not what a void return type means in C nor in C++. Similarly, in C++, it is already legal to return a void expression in the return statement of a function that returns void. Here we appear to be treating it as a value and copying it around, as that's what such returning means with all other types. These certainly point to void simply being a partially specified unit type rather than something else. Not that any of this matters, because we are not trying to put a label on what void in C++ currently is, but rather, we are trying to figure out what definition of void would most benefit the language in the future. Instead of niggling over possible interpretations of the words "void" or "emptiness," we should be looking at what definition of void with respect to C++ actually aids in developing software. We have actual usage and tangible examples for why it is extremely useful for void to simply be a regular type, regardless of whatever label you want to give it. I have seen NO tangible examples from the opposing crowd for why it should be something less.

As an aside, in C, when void was specified, a full definition of void didn't really matter much because the use-cases for void being regular come about when writing more abstract, generic code, which is less common in C outside of sophisticated macros. It doesn't really matter much in C because the uses for a complete void type don't usually come up in practice. Because of this, I'd personally argue that the nature of void as an incomplete type seems much more incidental due to there not having been obvious use-cases for it at the time (we have use-cases now). Even if the choice was NOT incidental, there are very strong reasons that show that it is much more beneficial to treat it as a regular type, so regardless of how you want to interpret its current meaning or reason for being, there are very clear benefits to making it complete and regular. You are only hurting progress and people writing actual code if you ignore that.

On Wed, Aug 5, 2015 at 6:54 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
    void foo();

    int main()
    {
        foo();
        return 0;
    }

    void foo(void)
    {
        return;
    }

(Note in particular that making void a regular type - i.e. 'struct
void{}' - WILL BREAK THIS! NOT ACCEPTABLE!)

No, you just specify that "return;" is shorthand for "return void();". No one is suggesting that we break code like this. I don't think anyone in this thread was under that assumption. This isn't really much of a change, since you don't have to write "return;", but rather, you can already, in valid C++, write "return g();", where "g" is another function that returns void. It's more just syntactic sugar. Of note, if you really wanted to, you could even generalize that notion to mean that "return;" is just syntactic sugar for "return T();" where T is the return value of the function (I'm not suggesting that we do this in the language, I'm just using it as an example of its interpretation as syntactic sugar). 
 
On Wed, Aug 5, 2015 at 6:54 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
Rather (without getting into some of the more esoteric template
questions), it seems like the following should be made legal:

    // allow void declaration; initialization is irrelevant
    void x;

    // allow void assignment; exactly equivalent to:
    //     (void)foo(); void y;
    void y = foo();

    // allow deduction from void
    auto z = x;

    // allow passing void declaration as argument to function
    // taking zero arguments
    foo(x);

    // allow comparing void declarations (see also relational operators
    // for Python's 'None')
    assert(x == y);
    assert(!(x != y));

    // (optional) "allow" taking the address of a void declaration
    auto p = &x;
    assert(p == nullptr); // ...but 'x' has no storage

This is just silly. What are you gaining by arbitrarily limiting to these operations like this and defining them in this manner? Please refer to the many actual examples that I've accumulated throughout this thread (including why you shouldn't have argument collapsing) and the 0 examples that you have provided regarding the benefits of your representation for void in C++. Unless you show why in practice what you are proposing improves over void simply being a regular type, and actually succeeds at removing the special casing of void in generic programming, then the suggested behavior is providing no visible worth. So far you have done neither. Don't dictate what void "should" be. Deduce what void should be based on what makes it easy to write meaningful and powerful code.
 
On Wed, Aug 5, 2015 at 6:54 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
In particular, I'm
concerned that there doesn't seem to be enough discussion how to
preserve calling a function with argument list "(void)" with no
arguments, or calling a function taking no arguments with a single
argument of type 'void'.

??? What is "enough." This was already talked about pretty extensively. It is not proposed that void foo(void) {} changes meaning, because that would be a huge breaking change. There have been at least 3 suggestions presented in 3 different replies for how to actually represent a unary function taking void.

Matt Calabrese

unread,
Aug 5, 2015, 3:03:41 PM8/5/15
to ISO C++ Standard - Future Proposals
On Wed, Aug 5, 2015 at 11:31 AM, Matt Calabrese <cala...@google.com> wrote:
On Wed, Aug 5, 2015 at 6:54 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
    void foo();

    int main()
    {
        foo();
        return 0;
    }

    void foo(void)
    {
        return;
    }

(Note in particular that making void a regular type - i.e. 'struct
void{}' - WILL BREAK THIS! NOT ACCEPTABLE!) 

No, you just specify that "return;" is shorthand for "return void();". No one is suggesting that we break code like this. I don't think anyone in this thread was under that assumption. This isn't really much of a change, since you don't have to write "return;", but rather, you can already, in valid C++, write "return g();", where "g" is another function that returns void. It's more just syntactic sugar. Of note, if you really wanted to, you could even generalize that notion to mean that "return;" is just syntactic sugar for "return T();" where T is the return value of the function (I'm not suggesting that we do this in the language, I'm just using it as an example of its interpretation as syntactic sugar). 

I just looked back and noticed you mean the void foo(); and void(void) {} difference. I thought you meant the return statement. I went over this later on in this reply anyway, but as mentioned several times in previous replies in this very thread, we are not proposing breaking the code that you've shown. No meaning would change here because the implications would be too great. Instead, if you are in a place where void is not a dependent type, it has the current meaning. If dependent, which is where we actually care about this and where void would not produce a valid function declaration in C++, it means unary void. If you want to represent a unary void function in a non-dependent context you'd just use some other syntax, which is unfortunate, but fine, and doesn't hurt generic code. There have been multiple suggestions for what that syntax would be.
It is loading more messages.
0 new messages