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);
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
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?
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.
A variable of type void
- occupies no space (sizeof(void) == 0)
- 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).
(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).
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++.
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:
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.
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.
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.
- 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.
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
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).
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);
}
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
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.)
There is no need to support "collapsing" void arguments
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.
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.
decltype is a C++11 trick. Please look at the large codebase of pre-C++11
SFINAE.
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.
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).
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 whetherstd::tuple<int, void, int>orstd::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==!
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. formsvoid 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 nominatetemplate<class T> struct simple_future {regularize<T> rt;void set(T p) { rt.set(p); }};
...Or, something else needs to happen, which I merely haven't thought of yet.
On 2015–08–03, at 12:41 PM, Arthur O'Dwyer <arthur....@gmail.com> wrote:I think it's extremely debatable whetherstd::tuple<int, void, int>orstd::array<void, 3>ought to be permitted by the language.
template<class T> struct simple_future {regularize<T> rt;void set(T p) { rt.set(p); }};
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 whetherstd::tuple<int, void, int>orstd::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.
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
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 whetherstd::tuple<int, void, int>orstd::array<void, 3>ought to be permitted by the language.
They should be permitted and they should be equivalent of `void`.
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?
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.
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 whetherstd::tuple<int, void, int>orstd::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.
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.
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 whetherstd::tuple<int, void, int>orstd::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.
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.
If one of the functions return `void` then the function should return `void`.
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
Well, for the case of a tuple it doesn't inject extra state(because `x * 1 = 1`).
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.
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'"; });
For what it's worth, I think we have enough unit types in C++ already, and don't see why we need one more.
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?}
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.
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:]
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].
using Void = void;void foo() {} // foo0void foo(Void) {} // foo1
void bar() {} // bar0template<class T>void bar(T) {} // bar1void baz(Void) {}int main(){foo(); // calls foo0foo(void()); // calls foo1bar(); // calls bar0bar(void()); // calls bar1, deducing T as void
baz(); // calls baz
using Void = void;
void foo() {} // foo0void foo(Void) {} // foo1
In my world this is a violation of the ODR; foo0 and foo1 are the same function.
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();
The question I was asking in the previous message is: If bar0 was not declared, would bar(); call bar1?
baz(); // calls bazOf course; this has always been the case.
--
---
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/.
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.
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
On 2015-08-04 08:55, Arthur O'Dwyer wrote:
Being aware that this is currently the case I wonder if it is a good idea?using Void = void;
void foo() {} // foo0void foo(Void) {} // foo1
In my world this is a violation of the ODR; foo0 and foo1 are the same function.
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.
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.
I understand that you want to be able to construct such an object via std::make_tuple(1, void()). I consider that part controversial.
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.
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) {}/////
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.
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);
Altermatively just give the void parameter a name. A named parameter shouldn't catch the special case of meaning an empty parameter list.
--
---
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.
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.
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:
Being aware that this is currently the case I wonder if it is a good idea?using Void = void;
void foo() {} // foo0void foo(Void) {} // foo1
In my world this is a violation of the ODR; foo0 and foo1 are the same function.
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--
---
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.
--
---
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.
Zach
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.
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
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.
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";
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:
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.
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>.
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
--
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.
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.
--
Zach
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.
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.
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 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.
--
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.
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.
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.
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*`).
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;
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.
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
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?
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
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'.
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).