Discussion:
[std-proposals] One variable init form to rule them all, via mandatory elision
Mark A. Gibbs
2015-09-09 23:27:17 UTC
Permalink
Scott Meyers's recent post on initialization has brought up a long-time pet
peeve of mine: defining new objects. My beef is not that there are multiple
ways to do it, it's that there is no single way that Just Works(tm)
everywhere, and does the right thing, with robust syntax. The "Almost
Always auto" style proposed by Herb Sutter comes close, but there are some
gotchas with it. It seems to me there is a very simple way to remove those
gotchas, but I've never seen it proposed.

I'm going to start by explaining why I've chosen AAA as the basis for this
proposal. If you're already down with AAA, you can skip to the tl;dr at the
bottom.

Meyers enumerates 4 different ways to declare and initialize a new
variable. With "type" being either a type name or "auto", and "val" being a
lvalue of type "type", they are:
type v = val; // #1
type v(val); // #2
type v = {val}; // #3
type v{val}; // #4

Let's just write #2 off immediately, because of the most vexing arse -
pardon the typo.

#4 is deeply problematic, because the behaviour has changed between C++14
and C++17. So let's drop that, too.

#3 has different behaviour whether "type" is an actually a type name or "
auto". So let's put that aside for the moment.

That leaves us with:
type v = val;

Which, using int, can be either:
int v = 0; // (a)
auto v = 0; // (b)

(a) is fine, but somewhat redundant and occasionally dangerous - if the
type isn't the same on the left and right, you may end up with a conversion
that may be expensive, or may lose information.

So that brings us down to (b), and the whole train of logic above turns to
be a reiteration of the "Almost Always auto" (AAA) argument. For those who
aren't familiar, the AAA argument is that variables should always be
defined using the pattern:
auto v = /* initial value */;

The benefits of this include:

- No unexpected conversions.
- Impossible to have an uninitialized variable.
- Consistent with other modern constructs, easy to read, hard to
misunderstand.


Generally speaking, if you already have a value and you just want to
initialize a copy or move it, you can just use:
auto v = val;

But if you don't have a value, or if you do but you want to be explicit
about the type, you can use:
auto v = type{val}; // or any other constructor arguments, including none

In cases where you *REALLY* want that type, and you don't care if you're
narrowing - or when you *really* want to use a specific constructor, and
the arguments are not just a initializer list of values - then you can use:
auto v = type(val); // Most vexing parse not a problem

The C++ standard allows compilers to elide the copy construction and move
assignment (really a move construction) to a single construction... *even
though it may produce different observable behaviour*. It is (AFAIK) the
only optimization the standard allows that can do that. That means that:
auto v = val; // a single copy construction
auto v = type{val}; // can be a single copy construction
auto v = type(val); // can be a single copy construction

And all modern compilers worth mentioning do the elision.

This also means that:
auto v = type{}; // can be just default construction

So it seems we have a winner for the one, true variable definition format.

But not so fast....

As Meyers noted, this pattern has one case where it doesn't work: types
that are non-copyable and non-movable.

The reasoning that even though the standard *allows* the move (or copy)
construction to be elided, it doesn't *require* it. And compilers have to
pretend they're still going to do it, even though they're not. That is why
this won't compile:
auto v = std::atomic<int>{};

But this restriction is silly and pedantic. There is no way the line above
can reasonably be interpreted as anything else but "I want v to be a
default constructed std::atomic<int>".

And if anyone wants to try to argue that someone might actually want to do
a default construction then move construction when writing that, I call
shenanigans. Because that's not what they're going to get - in any compiler
of note - and the standard blesses this.

Put yourself in the shoes of a C++ newbie who's just learning the language.
Wanting to explore constructors and destructors, ze writes this simple
class:

struct foo
{
foo() { std::cout << "constructing foo\n"; }
~foo() { std::cout << "destructing foo\n"; }

foo(foo const&) = delete;
foo& operator=(foo const&) = delete;

foo(foo&&) = delete;
foo& operator=(foo&&) = delete;
};

int main()
{
foo f;
}

// Output:
// constructing foo
// destructing foo

So far, no problems. But then ze decides to try using the AAA pattern to
define "f":

int main()
{
auto f = foo{};
}

This fails to compile, and the compiler explains that it's because "foo" is
non-movable and non-copyable. "Ah!" says our newbie, thinking ze's learned
something useful. "Well this makes perfect sense! I suppose I'm doing a
construction on the right, and then an assignment to the variable on the
left. Well played C++. Well, now let me experiment to see which special
member function gets used to do the transfer from the right side to the
left."

So now ze writes this:

struct foo
{
foo() { std::cout << "constructing foo\n"; }
~foo() { std::cout << "destructing foo\n"; }

foo(foo const&) { std::cout << "copy constructing foo\n"; }
foo& operator=(foo const&) { std::cout << "copy assigning foo\n"; return *
this; }

foo(foo&&) { std::cout << "move constructing foo\n"; }
foo& operator=(foo&&) { std::cout << "move assigning foo\n"; return *this;
}
};

int main()
{
auto f = foo{};
}

And how is our diligent newbie rewarded for zes experimentation? With this:

// Output:
// constructing foo
// destructing foo

"What... what the flagnar? None of the special functions are used? How can
this be? The compiler said I needed to add them! Why would the compiler
insist on having them if it never uses them! Is this a compiler bug? No?
This absurdity is standard behaviour? To hell with C++. Bring me a Ruby
book."

The cause of all this confusion is the requirement that statements of the
form "type v = type{/*params*/};" or "auto v = type{/*params*/};" (or with
parentheses rather than braces in both cases) *MUST* be interpreted as a
construction on the right, then *another* (move/copy) construction to the
variable on the left (and then a destruction of the temporary)... even
though at the same time compilers are free to elide it all down to a single
construction.

What if, instead, statements of the form "type v = type{/*params*/};" or "auto
v = type{/*params*/};" were interpreted as a single construction, of the
variable "v" which has type "type"?

This would only apply to any statement of the form "XXX varname =
YYY{/*arguments*/};" or "XXX varname = YYY(/*arguments*/);", provided that "
XXX" and "YYY" deduce to the same type (which is obviously true if "XXX" is
"auto"). It would not apply to, for example "float v = int{42};". It *would*
apply to "using number = int; number v = int{42};". It would be exactly
equivalent to "YYY varname{/*arguments*/};" or "YYY varname(/*arguments*/);"
(except, in the latter case, it will not be subject to the most vexing
parse when that applies).

For backwards compatibility, when "type" is movable or copyable, compilers
still have the leeway to implement it as a construction then a move
construction. I don't know of any compilers that actually do that, but,
just in case. That means that the meaning of any code that compiles today
will not change. The only thing that will change is the code that currently
doesn't compile today - when "type" is non-movable and non-copyable - will
now compile (and obviously will only be interpreted as a single
construction, because it can't possibly be interpreted as a construction
then move/copy). I can't imagine this adds any complexity to compilers, and
in fact *removes* a completely unnecessary check that they do today (the
check for move/copy constructability).

The result is we would get a way to define initialized variables that works
consistently in every case, is fairly robust against mistakes and typos,
and has obvious and logical semantics:

int i = 0; // an int
int i = int{0}; // identical to first line (does narrowing check)
int i = int(0); // identical to first line (no narrowing check)
auto i = 0; // identical to first line
auto i = int{0}; // identical to first line (does narrowing check)
auto i = int(0); // identical to first line (no narrowing check)

int i = int{}; // default construction
auto i = int{}; // identical to above

// Same semantics with types with UDLs
std::string s{"foo"}; // std::string
auto s = std::string{"foo"}; // identical to first line
auto s = "foo"s; // using a UDL, (effectively) identical

std::string s = std::string{}; // default construction
auto s = std::string{}; // identical to above

// Non-copyable, non-movable types, too
std::atomic<int> i = 0; // an std::atomic<int>
std::atomic<int> i = std::atomic<int>{0}; // identical to first line
std::atomic<int> i = std::atomic<int>(0); // identical to first line
auto i = std::atomic<int>{0}; // identical to first line
auto i = std::atomic<int>(0); // identical to first line

std::atomic<int> i = std::atomic<int>{}; // default construction
auto i = std::atomic<int>{}; // identical to above

auto a = std::atomic<int>{42};
std::atomic<int> b = a; // won't compile (non-copyable)
auto b = a; // same
std::atomic<int> b = std::move(a); // won't compile (non-movable)
auto b = std::move(a); // same

auto func() -> std::atomic<int>;
std::atomic<int> b = func(); // won't compile (non-movable)
auto b = func(); // same
std::atomic<int> b = std::atomic<int>{func()}; // Won't compile, because
the
// construction on the right
// is invalid. Would work if
// func() return an int.
auto b = std::atomic<int>{func()}; // same

// Familiar behaviours (that already assume elision) are unchanged
std::vector<int> v = std::vector<int>{}; // default construction
auto v = std::vector<int>{}; // same

std::vector<int> v = std::vector<int>{2, 3}; // list construction { 2, 3 }
auto v = std::vector<int>{2, 3}; // same

std::vector<int> v = std::vector<int>(2, 3); // constructs vector { 3, 3 }
auto v = std::vector<int>(2, 3); // same

// If you stick with the policy to always use auto on the left,
// even the initializer list issue that gnaws on Meyers becomes clear:
auto i = 0; // i is int (what else would it be?)
auto i = {0}; // i is initializer_list<int> (what else would it be?)
auto i = int{0}; // i is int (what else would it be?)

// It only gets weird if you do:
int i = 0; // Clear
int i = {0}; // What? You want to assign an initializer_list<int> to an
int?
// Well, I guess it works if there's only one int in the
list...
int i = int{0}; // Clear, but redundant
// Plus all the headaches mentioned in N4014
// But that's a style issue.

So here's the tl;dr bullet point version:

- The AAA style is awesome.
- It has only one gotcha - it fails for non-copyable, non-movable types.
- The language already allows the move/copy to be elided for
copyable/moveable types, and all compilers do that. So the move/copy
requirement is unnecessary. Let's ditch it.
- Let all statements of the form "XXX v = YYY{/*args*/};" where "XXX"
resolves to the same type as "YYY" (which includes when "XXX" is "auto",
obviously), be exactly equivalent to "YYY v{/*args*/};".
- Let all statements of the form "XXX v = YYY(/*args*/);" where "XXX"
resolves to the same type as "YYY" (which includes when "XXX" is "auto",
obviously), be exactly equivalent to "YYY v(/*args*/);"... ignoring the
most vexing parse.
- For the sake of backward compatibility, give compilers the freedom -
when the type is copyable and/or movable - to interpret "XXX v =
YYY{/*args*/};" or "XXX v = YYY(/*args*/);" as a construction followed
by a move/copy construction. (None do, and none likely will, but just in
case. This behaviour could be deprecated, I suppose.)
- In other words, the currently standard-blessed optimization of eliding
the unnecessary temporary construction and move would become not an
optimization, but "the way it's done". Not eliding would become a
"tolerated pessimization".
- Won't change the meaning of any old code. Will make code that
currently won't compile - for silly, pedantic reasons - functional.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Ville Voutilainen
2015-09-09 23:34:15 UTC
Permalink
On 10 September 2015 at 02:27, Mark A. Gibbs
Post by Mark A. Gibbs
But not so fast....
As Meyers noted, this pattern has one case where it doesn't work: types that
are non-copyable and non-movable.
The reasoning that even though the standard allows the move (or copy)
construction to be elided, it doesn't require it. And compilers have to
pretend they're still going to do it, even though they're not. That is why
auto v = std::atomic<int>{};
But this restriction is silly and pedantic. There is no way the line above
can reasonably be interpreted as anything else but "I want v to be a default
constructed std::atomic<int>".
The EWG disagreed:
http://cplusplus.github.io/EWG/ewg-closed.html#124
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Mark A. Gibbs
2015-09-09 23:48:49 UTC
Permalink
Post by Ville Voutilainen
On 10 September 2015 at 02:27, Mark A. Gibbs
Post by Mark A. Gibbs
But not so fast....
As Meyers noted, this pattern has one case where it doesn't work: types
that
Post by Mark A. Gibbs
are non-copyable and non-movable.
The reasoning that even though the standard allows the move (or copy)
construction to be elided, it doesn't require it. And compilers have to
pretend they're still going to do it, even though they're not. That is
why
Post by Mark A. Gibbs
auto v = std::atomic<int>{};
But this restriction is silly and pedantic. There is no way the line
above
Post by Mark A. Gibbs
can reasonably be interpreted as anything else but "I want v to be a
default
Post by Mark A. Gibbs
constructed std::atomic<int>".
http://cplusplus.github.io/EWG/ewg-closed.html#124
That doesn't seem to be the same point.

// I am talking about, for example. either of
tuple<int> t = tuple<int>{0};
auto t = tuple<int>{0};
// I am not talking about:
tuple<int> t = {0};
// Which is what N4014 was about
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Ville Voutilainen
2015-09-10 00:26:33 UTC
Permalink
On 10 September 2015 at 02:48, Mark A. Gibbs
Post by Mark A. Gibbs
Post by Ville Voutilainen
Post by Mark A. Gibbs
auto v = std::atomic<int>{};
But this restriction is silly and pedantic. There is no way the line above
can reasonably be interpreted as anything else but "I want v to be a default
constructed std::atomic<int>".
http://cplusplus.github.io/EWG/ewg-closed.html#124
That doesn't seem to be the same point.
// I am talking about, for example. either of
tuple<int> t = tuple<int>{0};
auto t = tuple<int>{0};
tuple<int> t = {0};
// Which is what N4014 was about
The point is that EWG disagreed with the part which you claimed cannot be
reasonably be interpreted as anything else but default-constructing. We didn't
want to remove the semantic check for copy/move even for the case where the
copy/move is elided. That semantic check is what makes your example
be rejected.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Mark A. Gibbs
2015-09-10 01:52:05 UTC
Permalink
Post by Ville Voutilainen
The point is that EWG disagreed with the part which you claimed cannot be
reasonably be interpreted as anything else but default-constructing. We didn't
want to remove the semantic check for copy/move even for the case where the
copy/move is elided. That semantic check is what makes your example
be rejected.
It still doesn't look like the same point to me. The item you pointed to
appears to be about explicit versus non-explicit construction, not
copyability (or movability). I'm not talking about "type t = {};", I'm
talking about "type t = type{};". What I'm proposing has absolutely no
impact on explicitness, nor is it impacted by it. If there is actually an
argument relevant to copy/movability, I'd like to hear it.

I have never seen anyone, anywhere, suggest that using the construct "auto
x = T{};" (or "T x = T{};") is a way to test for copyability (or
movability) in generic code. I have never seen anyone say: "I write 'T
x{...};' in generic code when I only require that constructor signature,
and I write 'auto x = T{...};' to additionally constrain for copy and/or
movability." In fact, I would say anyone who did that was just creating
maintenance headaches. By contrast, I have seen several beginners surprised
and annoyed when it doesn't work for some types, like atomics, mutexes,
locks, and other RAII-implementing types. (Even Meyers highlights this
frustration, as his "Question 2".)

When someone wants to require copy/movability there are plenty of other,
less obtuse, ways to do it:
auto t2 = t1
auto t2 = T{t1};
T t2{t1};
T t2 = t1;
T t2 = T{t1};
// etc., and of course traits, and eventually concepts
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
David Krauss
2015-09-10 02:53:44 UTC
Permalink
Let me start by saying, I hate the looseness of the elision rule too too. If I had more time, I’d be proposing to require elision where it’s de-facto standard.
Post by Mark A. Gibbs
Scott Meyers's recent post
http://scottmeyers.blogspot.com/2015/09/thoughts-on-vagaries-of-c-initialization.html <http://scottmeyers.blogspot.com/2015/09/thoughts-on-vagaries-of-c-initialization.html>
Post by Mark A. Gibbs
on initialization has brought up a long-time pet peeve of mine: defining new objects. My beef is not that there are multiple ways to do it, it's that there is no single way that Just Works(tm) everywhere, and does the right thing, with robust syntax. The "Almost Always auto" style proposed by Herb Sutter comes close, but there are some gotchas with it. It seems to me there is a very simple way to remove those gotchas, but I've never seen it proposed.
Elision hasn’t been mentioned yet :( .

No syntax Just Works™ because classes really do different things. Statements should not have unbounded meaning.
Post by Mark A. Gibbs
I'm going to start by explaining why I've chosen AAA as the basis for this proposal. If you're already down with AAA, you can skip to the tl;dr at the bottom.
type v = val; // #1
type v(val); // #2
type v = {val}; // #3
type v{val}; // #4
Let's just write #2 off immediately, because of the most vexing arse - pardon the typo.
No, MVP happens only with an empty argument list. Otherwise #2 looks like a function call, so it’s appropriate for constructors that behave more like functions and less like aggregate initialization. As a special case, aggregating no values is the same as calling a nullary function. Adding values/parameters, the concepts diverge.

#2 is sometimes required to disambiguate an initializer list (aggregate-like) from a parameter list (function-like).
Post by Mark A. Gibbs
#4 is deeply problematic, because the behaviour has changed between C++14 and C++17. So let's drop that, too.
Huh? What change? I think that if the committee agreed with that consequence, they wouldn’t have changed it.

So now you’re saying that there should always be an equals sign, dropping all direct-initialization. This is drifting far away from the specific topic of elision.

An equals sign is reminiscent of mathematical values: It joins two mathematical objects and asserts their equality. I prefer to avoid it for things that don’t have values.

Ah, now Sutter’s article has loaded on my slow connection. C++17 deprecates treating a braced-init-list as an initializer_list value when there’s no equals sign. This makes sense, because initializer_list is definitely forming a new value there (although it doesn’t always behave with value semantics).
Post by Mark A. Gibbs
#3 has different behaviour whether "type" is an actually a type name or "auto". So let's put that aside for the moment.
type v = val;

 which, for nontrivial expressions that we encounter most often, suggests that val is a factory function call.
When discussing elision, int, being trivially copyable, is not a good default illustrative type.
Post by Mark A. Gibbs
int v = 0; // (a)
auto v = 0; // (b)
(a) is fine, but somewhat redundant and occasionally dangerous - if the type isn't the same on the left and right, you may end up with a conversion that may be expensive, or may lose information.
Hence almost always auto. A lot of more-experienced programmers aren’t 100% on that bandwagon.
Post by Mark A. Gibbs
auto v = /* initial value */;
No unexpected conversions.
Some sort of conversion is needed to obtain a value from an expression template object, so this isn’t axiomatic.
Post by Mark A. Gibbs
Impossible to have an uninitialized variable.
Consistent with other modern constructs, easy to read, hard to misunderstand.
If you extend the meaning of #1 to include #2, #3, and #4, won’t that increase the potential for misunderstanding?

Syntax has no inherent value by itself; it just stands for semantic concepts. If you think that #1 ain’t broke, why fix it? (Still no mention of elision in this post.)
Post by Mark A. Gibbs
auto v = val;
auto v = type{val}; // or any other constructor arguments, including none
Or a factory function, which furthermore needs to worry about the return statement.

(snip)
Post by Mark A. Gibbs
But not so fast....
As Meyers noted, this pattern has one case where it doesn't work: types that are non-copyable and non-movable.
Such types tend to lack value semantics. Not a coincidence that the equals sign becomes undefined.
Post by Mark A. Gibbs
auto v = std::atomic<int>{};
But this restriction is silly and pedantic. There is no way the line above can reasonably be interpreted as anything else but "I want v to be a default constructed std::atomic<int>”.
No, atomic variables have special synchronization requirements. They’re a refinement of volatile. I look at that statement and I see two distinct variables joined by an equals sign. Take away the object-ness of std::atomic and it becomes conceptually vague.
Post by Mark A. Gibbs
And if anyone wants to try to argue that someone might actually want to do a default construction then move construction when writing that, I call shenanigans. Because that's not what they're going to get - in any compiler of note - and the standard blesses this.
They don’t want that, and they won’t get it, because it’s nonsense. This argument is a logical fallacy of false supposition. The language is correct to prevent having two objects in that statement. Yet it would be incorrect to allow the statement to work with one object, because std::atomic is a type where one object emphatically never represents two values.
Writing random code from no semantic premises, and guessing at meaning from the perspective of a naive person, is not part of language design.

The solution to this problem is that newbies shouldn’t write non-value-semantic classes. Deleting the move constructor is a red flag, indicating that programming assistance is needed.

(Snip — the newbie did not seek help. This seldom ends well in any language.)


Here’s my take:

The problem with copy-initialization is that it’s sometimes used with no equals sign, such as in passing, returning, and throwing. The language treats it as a conservative choice when it’s impossible to guarantee that two values are the same object. However, this guarantee can always be made when passing, throwing, or returning a prvalue. The language/ABI interface is interfering with the programmer’s stated intentions.

But the elision problem isn’t limited to copy-initialization at all. Consider:

struct s {
explicit s() = default;
s( s && ) = delete;
};

s f() { return s{}; } // No equals sign nor conversion, but still copy-initialization. Excessive language conservatism.

s q{ f() }; // Direct-initalization can also be ill-formed due to lack of move constructor.

I posit that the above snippet should work. It goes far enough out of its way to avoid suggesting value semantics, and all implementations can guarantee that the move constructor isn’t needed.

Unfortunately, I don’t have time right now for deeper discussion, much less to write a proposal.

And BTW, I agree with Ville’s that your proposal is closely aligned with N4014. They both result from blaming the problems of non-value-semantic classes on the equals sign instead of the class. The equals sign is a safety mechanism to protect you from thorny types; getting rid of its significance will only open the gates to more nastiness.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
David Krauss
2015-09-10 02:59:01 UTC
Permalink
Post by David Krauss
No, MVP happens only with an empty argument list.
inb4 the pedants, with int q( int() ), there’s still an empty list in the inner expression/declarator, and disambiguation is accomplished by changing it, specifically, to braces: int q( int{} ).
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Ville Voutilainen
2015-09-10 05:14:28 UTC
Permalink
Post by Mark A. Gibbs
#4 is deeply problematic, because the behaviour has changed between C++14
and C++17. So let's drop that, too.
Huh? What change? I think that if the committee agreed with that
consequence, they wouldn’t have changed it.
That's N3922..
Post by Mark A. Gibbs
So now you’re saying that there should always be an equals sign, dropping
all direct-initialization. This is drifting far away from the specific topic
of elision.
An equals sign is reminiscent of mathematical values: It joins two
mathematical objects and asserts their equality. I prefer to avoid it for
things that don’t have values.
Ah, now Sutter’s article has loaded on my slow connection. C++17 deprecates
treating a braced-init-list as an initializer_list value when there’s no
equals sign. This makes sense, because initializer_list is definitely
forming a new value there (although it doesn’t always behave with value
semantics).
..but there's no deprecation, and N3922 applies as a Defect Report all the way
back to C++11, and gcc implements it in its C++11 mode. MSVC doesn't
have conformance modes, but they also already ship with the N3922
change included.

Then again, even with N3922,

auto x{atomic<int>{}};

still doesn't work because even there, a semantic check for copy/move
is performed.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
David Krauss
2015-09-10 05:24:49 UTC
Permalink
Post by Ville Voutilainen
..but there's no deprecation, and N3922 applies as a Defect Report all the way
back to C++11, and gcc implements it in its C++11 mode. MSVC doesn't
have conformance modes, but they also already ship with the N3922
change included.
OK, retroactive elimination and wiping-out. Stronger than deprecation.
Post by Ville Voutilainen
Then again, even with N3922,
auto x{atomic<int>{}};
still doesn't work because even there, a semantic check for copy/move
is performed.
Right. I don’t favor that stylistically, but I’ll take it if it can’t be surgically separated from non-movable factory functions which are a hole in the language.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Matthew Fioravante
2015-09-10 16:34:51 UTC
Permalink
Post by Ville Voutilainen
still doesn't work because even there, a semantic check for copy/move
is performed.
Right. I don’t favor that stylistically, but I’ll take it if it can’t be
surgically separated from non-movable factory functions which are a hole in
the language.
I would love to have factory functions for non-movable types. It seems that
with return value optimization, most of the work is already
done. Unfortunately I think adding support would require new syntax of some
kind.

Maybe an attribute [[emplace]]. This would force RVO, enabling support for
"returning" a non-movable object and also allowing the programmer to
enforce that RVO is used for functions which return movable types.

[[emplace]] Widget makeWidget() { return Widget(); }
[[emplace]] auto x = makeWidget();
[[emplace]] auto x = std::atomic<int>{};

Of course with that approach people might just end up sprinkling
[[emplace]] in all of their functions to optimize and increase
compatibility...

Another option could be a new "inplace" initialization operator to use
instead of =. I don't have a full specification for such a thing but
it might look something like this:
static_assert(is_movable<Widget>::value == false);

Widget makeWidget() <=; //Declare an inplace return function that must do
RVO.
auto w <= makeWidget(); //inplace construct w
auto i <= std::atomic<int>{}; //inplace construct i

Such an operator could even be used for value semantic types to specify the
specific intent of direct construction vs copying. Op <= is a strawman for
bikeshedding, but I think it could work since the comparison <= doesn't
make sense in a new variable declaration or function signature.

In general the C++ language is designed around value semantics (i.e.
construct and move) but has pretty poor support for inplace construction of
immobile types. There are many (I would argue artificial) holes in the
language and library when you need to work with immobile types.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
David Krauss
2015-09-10 20:33:54 UTC
Permalink
I would love to have factory functions for non-movable types. It seems that with return value optimization, most of the work is already
done. Unfortunately I think adding support would require new syntax of some kind.
I think it would only require guaranteeing elision when initializing from (or returning) a prvalue of the same type.

Named return values would be icing on the cake, but that’s more complicated.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Richard Smith
2015-09-10 20:37:34 UTC
Permalink
Post by Matthew Fioravante
Post by Ville Voutilainen
still doesn't work because even there, a semantic check for copy/move
is performed.
Right. I don’t favor that stylistically, but I’ll take it if it can’t be
surgically separated from non-movable factory functions which are a hole in
the language.
I would love to have factory functions for non-movable types.
struct Immovable {
Immovable(int, int);
Immovable(const Immovable&) = delete;
void operator=(const Immovable&) = delete;
};

Immovable factory() {
return {1, 2};
}

auto &&val = factory();

It seems that with return value optimization, most of the work is already
Post by Matthew Fioravante
done. Unfortunately I think adding support would require new syntax of
some kind.
Maybe an attribute [[emplace]]. This would force RVO, enabling support for
"returning" a non-movable object and also allowing the programmer to
enforce that RVO is used for functions which return movable types.
[[emplace]] Widget makeWidget() { return Widget(); }
[[emplace]] auto x = makeWidget();
[[emplace]] auto x = std::atomic<int>{};
Of course with that approach people might just end up sprinkling
[[emplace]] in all of their functions to optimize and increase
compatibility...
Another option could be a new "inplace" initialization operator to use
instead of =. I don't have a full specification for such a thing but
static_assert(is_movable<Widget>::value == false);
Widget makeWidget() <=; //Declare an inplace return function that must do
RVO.
auto w <= makeWidget(); //inplace construct w
auto i <= std::atomic<int>{}; //inplace construct i
Such an operator could even be used for value semantic types to specify
the specific intent of direct construction vs copying. Op <= is a strawman
for bikeshedding, but I think it could work since the comparison <= doesn't
make sense in a new variable declaration or function signature.
In general the C++ language is designed around value semantics (i.e.
construct and move) but has pretty poor support for inplace construction of
immobile types. There are many (I would argue artificial) holes in the
language and library when you need to work with immobile types.
--
---
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
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-proposals/.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
David Krauss
2015-09-10 20:56:34 UTC
Permalink
Post by Richard Smith
struct Immovable {
Immovable(int, int);
This should be explicit. After all, it’s not a value-semantic class.
Post by Richard Smith
Immovable(const Immovable&) = delete;
void operator=(const Immovable&) = delete;
};
Immovable factory() {
return {1, 2};
Omitting Immovable from this line is exactly wrong.
Post by Richard Smith
}
auto &&val = factory();
Lifetime extension only works for scoped variables.

struct caps {
Immovable member { factory() };
Immovable * ptr = new Immovable { factory() };
};
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Richard Smith
2015-09-10 21:29:00 UTC
Permalink
Post by Richard Smith
struct Immovable {
Immovable(int, int);
This should be explicit. After all, it’s not a value-semantic class.
That is not the criterion I use for this decision. The question is, does
the pair of ints describe the value of the object (implicit constructor) or
does it describe a set of inputs to a /computation/ that will produce the
value of the object (explicit constructor)?
Post by Richard Smith
Immovable(const Immovable&) = delete;
void operator=(const Immovable&) = delete;
};
Immovable factory() {
return {1, 2};
Omitting Immovable from this line is exactly wrong.
That's just, like, your opinion, man. =)
Post by Richard Smith
}
auto &&val = factory();
Lifetime extension only works for scoped variables.
Yes. Guaranteed copy elision from temporaries would provide a complete
solution (and I might present a paper on that topic at Kona). Then we don't
need those fancy tricks, and we can instead just write the natural code:

Immovable factory() {
return Immovable{1, 2}; // no copy here
}
auto val = factory(); // no copy here either

struct caps {
Post by Richard Smith
Immovable member { factory() };
Immovable * ptr = new Immovable { factory() };
};
--
---
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
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-proposals/.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
David Krauss
2015-09-11 01:57:35 UTC
Permalink
Post by David Krauss
This should be explicit. After all, it’s not a value-semantic class.
That is not the criterion I use for this decision. The question is, does the pair of ints describe the value of the object (implicit constructor) or does it describe a set of inputs to a /computation/ that will produce the value of the object (explicit constructor)?
That's just, like, your opinion, man. =)
That was meant as a reference my first message. Value semantics are a bit subjective, but they’re anti-correlated with a deleted move constructor.

If you can’t have this:

Immovable do_ = Immovable{ 2, 2 };

then how can this be allowed?

Immovable do_ = { 2, 2 };

The only conceptual wiggle room is the fleeting idea that the list itself is a value.
Hooray! Let me know if I can help.
Post by David Krauss
Immovable factory() {
return Immovable{1, 2}; // no copy here
}
auto val = factory(); // no copy here either
Hmm, should that be allowed with copy-initialization, or should auto val{ factory() } be required?
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Richard Smith
2015-09-11 02:03:27 UTC
Permalink
Post by Richard Smith
Post by David Krauss
This should be explicit. After all, it’s not a value-semantic class.
That is not the criterion I use for this decision. The question is, does
the pair of ints describe the value of the object (implicit constructor) or
does it describe a set of inputs to a /computation/ that will produce the
value of the object (explicit constructor)?
That's just, like, your opinion, man. =)
That was meant as a reference my first message. Value semantics are a bit
subjective, but they’re anti-correlated with a deleted move constructor.
Immovable do_ = Immovable{ 2, 2 };
then how can this be allowed?
Immovable do_ = { 2, 2 };
The only conceptual wiggle room is the fleeting idea that the list itself is a value.
Yes. Guaranteed copy elision from temporaries would provide a complete
solution (and I might present a paper on that topic at Kona). Then we don't
Hooray! Let me know if I can help.
I'll let you know once I have a draft.
Post by Richard Smith
Immovable factory() {
return Immovable{1, 2}; // no copy here
}
auto val = factory(); // no copy here either
Hmm, should that be allowed with copy-initialization, or should auto val{
factory() } be required?
With the approach I'm intending to follow, that will be valid (and that
seems necessary, given that 'return', argument passing, braced init lists,
and so on all use copy-initialization).
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
David Krauss
2015-09-11 02:22:38 UTC
Permalink
With the approach I'm intending to follow, that will be valid (and that seems necessary, given that 'return', argument passing, braced init lists, and so on all use copy-initialization).
The problem with copy-initialization is that it’s sometimes used with no equals sign, such as in passing, returning, and throwing. The language treats it as a conservative choice when it’s impossible to guarantee that two values are the same object. However, this guarantee can always be made when passing, throwing, or returning a prvalue. The language/ABI interface is interfering with the programmer’s stated intentions.
My guess at the rationale for copy-initialization being applied in those contexts is that the implementation detail of a copy might be required. But if we change that detail, the backwards application of syntax-oriented semantics might also change.

I might not ready to argue this in court, though :P .
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
'Matt Calabrese' via ISO C++ Standard - Future Proposals
2015-09-10 21:02:37 UTC
Permalink
On Thu, Sep 10, 2015 at 9:34 AM, Matthew Fioravante <
Post by Matthew Fioravante
Post by Ville Voutilainen
still doesn't work because even there, a semantic check for copy/move
is performed.
Right. I don’t favor that stylistically, but I’ll take it if it can’t be
surgically separated from non-movable factory functions which are a hole in
the language.
I would love to have factory functions for non-movable types.
struct Immovable {
Immovable(int, int);
Immovable(const Immovable&) = delete;
void operator=(const Immovable&) = delete;
};
Immovable factory() {
return {1, 2};
}
auto &&val = factory();
Okay, that's twisted!!!
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Mark A. Gibbs
2015-09-11 04:02:11 UTC
Permalink
Post by David Krauss
Elision hasn’t been mentioned yet :( .
The first 3/4 of the original post was just a whirlwind rush through the
rationale of why I'm proposing what I'm proposing, haphazardly glossing
over numerous and sundry details that aren't really relevant. To reiterate
the reasoning:

- There are myriad ways to define new objects.
- Every one of those ways has at least one fail case or gotcha.
- Most of the ways cannot be salvaged - the fail case or gotcha cannot
be fixed without creating other headaches.
- But there is one way that is almost perfect, and for which a fix is
plausible: the form "XXX x = YYY{...};", where "XXX" and "YYY" resolve
to exactly the same type. (You can replace the braces with parentheses,
too.)
- The only gotcha in that case is that it doesn't work for non-movable
and non-copyable types (though it does work for non-movable/copyable and
movable/non-copyable types).
- What makes this gotcha even more absurd is that, in reality, no move
or copy ever happens, due to elision.
- This elision, which changes the observable behaviour of the program,
is allowed by the standard, but not required... and this is why that form
doesn't work for non-movable/non-copyable types.
- So what my proposal is, is make that elision "mandatory", in effect -
or rather, reinterpret statements of the form "XXX x = YYY{...};" to be
a "definition statement", which is identical in meaning to "YYY x{...};"
(or you can substitute parentheses, and you won't get bitten by the MVP).
In other words, making it a new form of direct construction expression.
- What this proposal does is:
- Gives us one perfect way to direct define/define new variables that
works in all contexts, all the time, generic or not, no exceptions.
- With a syntax that is easy to understand, clear, familiar and
consistent with other types of construction (such as copy/move/conversion
construction "XXX x = val;").
- With all the flexibility you can possibly want (you can use braces
or parentheses, as you please, and they will select list constructors or
not as per current rules).
- Without breaking any currently legal code.

Note that this has nothing to do with factory functions - whether for
non-value types or not - and so is unrelated to the discussion going on
elsewhere.

Writing random code from no semantic premises, and guessing at meaning from
Post by David Krauss
the perspective of a naive person, is not part of language design.
The solution to this problem is that newbies shouldn’t write
non-value-semantic classes. Deleting the move constructor is a red flag,
indicating that programming assistance is needed.
(Snip — the newbie did not seek help. This seldom ends well in any language.)
Most of my best students are those who took the initiative to figure things
out in the language via experimentation. When you can't do that - or worse,
as in this situation, when experimentation leads you confidently to the
wrong answer - that means the language simply doesn't make sense. I don't
think the correct answer to that dilemma is "you shouldn't experiment". The
problem is not the student, it's the language.

Nothing rules out consulting an expert, of course, but the point is that
you have to know *when* to consult an expert, and what questions to ask.
You can't simply walk up to a language expert and say "just tell me
everything". All experts and help communities expect you to do your
homework and come prepared only with those questions you can't figure out
on your own. (And in this case, trying to figure it out on your own will
lead you to the wrong answer. Thus, we have a problem.)

Actually, making the language make more sense to beginners is my primary
motivation for proposing this change (but also, easier generic
programming). I would love to be able to tell the people I teach, on day 1
with no hemming or hawing or qualification: "This is how you create a new
object in C++. Just do this. Auto, name, equals, type, braces, semicolon.
Done. Want to initialize with a specific value? Stick it in the braces.
Yes, there are other ways that you'll certainly see in other people's code,
and in time - when we cover more topics - we'll talk about the various
pros, cons, and gotchas. For now, just do this. It works." Then I can move
on to teaching them actually useful things, and not have them come up to me
a couple days later, "um, I tried to use this lock guard, and...".

Simple things should be simple. There doesn't need to be a goblin lurking
in every single corner of the language.
Post by David Krauss
And BTW, I agree with Ville’s that your proposal is closely aligned with
N4014. They both result from blaming the problems of non-value-semantic
classes on the equals sign instead of the class. The equals sign is a
safety mechanism to protect you from thorny types; getting rid of its
significance will only open the gates to more nastiness.
Okay, I think I'm cluing in to what you and Ville are saying. When
referring to the relationship to N4014, you are not actually referring to
the actual content of the paper (or the note on why it was rejected), but
rather to what I assume is a discussion that went on behind the scenes.
That discussion was not just on the specific issue (of explicitness), but a
broader discussion about protecting the purity of separation between value
types and non-value types.

And I gather what you're saying is that you want the equals sign to be the
line of separation between value types and non-value types - that is, when
you're using an equal sign, you must be working with a value type.

Does that correctly summarize what you and Ville are getting at?

Assuming so, I have to disagree with the reasoning. I don't disagree with
maintaining a bright line of separation between value types and non-value
types (let's just go with VTs and NVTs) - what I disagree with is that the
equal sign is worth defending for this cause. I would say that battle is
over, and purity lost. Here's why:
atomic<int> x = {};

That works, but this doesn't:
atomic<int> x = atomic<int>{};

So logically that means that the first statement is not really constructing
an atomic on the right - it's (in essence) assigning (which is really a
conversion construction) the (empty) list object on the right to the atomic
on the left.

But if that's so, then why doesn't it work with types with an explicit
default constructor?

The fact that it fails when the constructor is explicit implies that there
must be (implicitly) an atomic<int> construction going on on the right. I
mean, you can't sanely argue that whatever is being constructed on the left
isn't explicitly an atomic<int>, right?

Yet if that is what is happening, then why does the second form fail? Isn't
it doing exactly the same thing. Only... *wince*... explicitly?

So the equal sign is already in a dire mess when it comes to drawing a
bright line between VTs and NVTs. Digging in and defending it for that
purpose seems quixotic.

I also don't see any point in blaming NVTs for the mess, and saying "types
should always be VTs... except when they can't possibly, and in that case,
damn them to hell". Or rather, "yes, this is a mess, but it's only a mess
when you use NVTs, so don't use them... unless you really, really have to,
and if you have to, well, just suck up and accept the pain" (which reminds
me of that old joke about the doctor and "it hurts when I do this"). I do
agree that you should strongly prefer value semantics for your types, but
the reality is that there are types for which that simply doesn't work, and
those types aren't exactly rare or obscure. (You can make a valid argument
against using mutexes and atomics all over the place - and maybe even locks
- because there are better options for concurrency and parallelism, but
what about scope guard objects? Like things that manage commit-or-rollback
for transaction-like operations.) Creating one universal way to create
objects really doesn't open the floodgates to making NVTs generally easy to
use. After all, if your coding standard specified always using non-auto
copy-/direct-list-initialization, that ship's already sailed anyway.

What I'm proposing is to change the way you think about the equal sign in
expressions of the form "XXX x = YYY{...};". Don't think of it as an
assignment operation (which, frankly, it already isn't - it doesn't call
any assignment operation, it does a (copy/move) construction). Think of it
not as a mathematical "equality" (which it already isn't really) but rather
as a "definition". Mentally replace it not with "takes the value of", but
rather "is".

And there is precedence for this in C++.
// This is not an operation on values
using type = other_type;
// Neither is this
namespace ns = verbose::name::space;

In other words, in the context of statements of the form "XXX x = YYY{...};",
where "XXX" and "YYY" resolve to exactly the same type, the equals doesn't
describe a transmission of value, but rather an alias. You can view it as
defining a label "x", which is interpreted as a "YYY" object (remember, "XXX"
resolves to the exact same type, but "XXX" might be "auto" whereas "YYY"
cannot be), that is bound to the temporary that is created on the right,
and the lifetime of the temporary is extended for the lifetime of the label
"x".

And this form of variable definition is not revolutionary. Many languages
have similar constructs
// Perl
my $x = initializer;

// OCaml, Rust
let x = initializer;

// JavaScript, Swift
var x = initializer;

// C++, conceptually
auto x = initializer;

Obviously the circumstances are very different in those other languages,
and I'm not suggesting the semantics be duplicated in C++. I'm just saying
the form is familiar. It just "looks like" you're creating the object on
the left, and describing an alias to it on the right.

Another side effect of this change would be creating a kind of beautiful,
simplistic consistency in the language. Here is what it would look like if
you are using explicit types (no "auto"):
type x = type{}; // (direct) default construction
type x = type(); // same
// Both of the above work for *all* types that have default constructors,
// regardless of whether it's explicit or not, regardless of whether the
type
// is movable and/or copyable. It Just Works(tm).

type x = type{...}; // (explicit, direct) list initialization
type x = type(...); // (explicit, direct) non-list initialization
// Both of the above work for *all* types that have the constructor with the
// given signature, regardless of whether it's explicit or not, regardless
of
// whether the type is movable and/or copyable. As with today, an
initializer
// list constructor gets priority in the first case, and a
non-initializer-list
// constructor gets priority in the second case. It Just Works(tm).

type x = lvalue; // copy construction (presumably)
type x = rvalue; // move construction (presumably)
type x = std::move(lvalue); // move construction (presumably)
type x = factory_function(); // move construction (presumably)
// The semantics of the above don't change - they only work for types that
// allow copy/move construction. These all Just Work(tm) for value types.

type x = 0; // int
type x = {0}; // int
// The semantics of the above don't change.

// The type names don't need to be identical, they just need to resolve to
the
// same type.
using alias = type;
alias x = type{}; // (direct) default construction
alias x = type(); // same
alias x = type{...}; // (explicit, direct) list initialization
alias x = type(...); // (explicit, direct) non-list initialization

Things get even simpler, and more elegant, when you use "auto":
auto x = type{}; // (direct) default construction
auto x = type(); // same
// Both of the above work for *all* types that have default constructors,
// regardless of whether it's explicit or not, regardless of whether the
type
// is movable and/or copyable. It Just Works(tm).

auto x = type{...}; // (explicit, direct) list initialization
auto x = type(...); // (explicit, direct) non-list initialization
// Both of the above work for *all* types that have the constructor with the
// given signature, regardless of whether it's explicit or not, regardless
of
// whether the type is movable and/or copyable. As with today, an
initializer
// list constructor gets priority in the first case, and a
non-initializer-list
// constructor gets priority in the second case. It Just Works(tm).

auto x = lvalue; // copy construction (presumably)
auto x = rvalue; // move construction (presumably)
auto x = std::move(lvalue); // move construction (presumably)
auto x = factory_function(); // move construction (presumably)
// The semantics of the above don't change - they only work for types that
// allow copy/move construction. These all Just Work(tm) for value types.

auto x = 0; // int
auto x = {0}; // std::initializer_list<int> (with one element, that is 0)
// which is different from using explicit type (but still
clear)
// The semantics of the above don't change.

// No issues with type aliasing

I can only think of two potential gotchas, but they're both rather esoteric
cases.

Case 1 is code that relies on the current behaviour:
template <typename T>
auto func()
{
// I expect this template function to only be instantiated for types that
are
// movable and/or copyable, and rather than expressing this via traits or
// concepts, I'm relying on the current interpretation of the following
lines:
T t1 = T{};
auto t1 = T{};
}

This function fails to compile today for non-movable/non-copyable types,
but will with my proposed change.

However, I have never seen any code that relied on that, and I would argue
any code that did was already a maintenance headache.

Case 2 is code that gets bitten by type aliasing:
// assuming type_1 is non-copyable and non-movable
using type_2 = type_1;

template <typename T, typename U>
auto func()
{
// I expect this template function to do a default construction of a
temporary
// U object, then a conversion construction of a T using that temporary.
// In fact, I *INSIST* on this behaviour, and even use a special
compiler, or
// compiler switches, to make it happen.
T t = U{};
}

func<type_1, type_2>();

This function - assuming a compiler that does no elision, or that elision
has been disabled - will always create the temporary then construct the
object with it under current rules. With proposed change, it will continue
to do that... except when T and U resolve to the same type. In that case,
the statement is interpreted as a direct default construction of "t".

But of course, who seriously turns off elision, or requires that it be
disabled? And if you really want that series of operations, relying on
compiler switches means your code is unportable anyway. You could get the
same behaviour, portably, by just writing it over two lines.

There are still some edge cases I'm not sure how to handle. For example,
how to handle the case in "XXX x = YYY{...};" where the two types differ
only in cv-qualification. Or referenceness. I'm inclined to say "ignore cv
differences ("x" will have the cv qualifiers of "XXX", not "YYY"), but
don't ignore referenceness (interpret that the same as today)".
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
David Krauss
2015-09-11 08:11:36 UTC
Permalink
Most of my best students are those who took the initiative to figure things out in the language via experimentation. When you can't do that - or worse, as in this situation, when experimentation leads you confidently to the wrong answer - that means the language simply doesn't make sense. I don't think the correct answer to that dilemma is "you shouldn't experiment". The problem is not the student, it's the language.
I didn’t say not to experiment, I said that you get into trouble when you lose value semantics. The problem with the example is that its “experiment” just seems to be a random jumble of keywords, and then you say that the result is surprising. An experiment is something that tests a hypothesis, and the example never said what the student was after.
Nothing rules out consulting an expert, of course, but the point is that you have to know when to consult an expert, and what questions to ask. You can't simply walk up to a language expert and say "just tell me everything". All experts and help communities expect you to do your homework and come prepared only with those questions you can't figure out on your own. (And in this case, trying to figure it out on your own will lead you to the wrong answer. Thus, we have a problem.)
In this case, the question would be, “why doesn’t the equals sign work”? And the answer would be, “the right-hand side doesn’t behave as a value.”
Actually, making the language make more sense to beginners is my primary motivation for proposing this change (but also, easier generic programming).
The flip side of regular syntax with loose semantic constraints is getting unexpected meaning from templates. Some things aren’t supposed to compile.
I would love to be able to tell the people I teach, on day 1 with no hemming or hawing or qualification: "This is how you create a new object in C++. Just do this. Auto, name, equals, type, braces, semicolon. Done. Want to initialize with a specific value? Stick it in the braces. Yes, there are other ways that you'll certainly see in other people's code, and in time - when we cover more topics - we'll talk about the various pros, cons, and gotchas. For now, just do this. It works." Then I can move on to teaching them actually useful things, and not have them come up to me a couple days later, "um, I tried to use this lock guard, and
".
There’s a language for that: Java. (Except, scalars in Java are new objects without using the new keyword.)

The extra bits of C++ are useful to make it a richer language. We can’t just sweep them under the rug. You can use AAA as far as value semantics go, but that won’t get you a lock guard.
Simple things should be simple. There doesn't need to be a goblin lurking in every single corner of the language.
And BTW, I agree with Ville’s that your proposal is closely aligned with N4014. They both result from blaming the problems of non-value-semantic classes on the equals sign instead of the class. The equals sign is a safety mechanism to protect you from thorny types; getting rid of its significance will only open the gates to more nastiness.
Okay, I think I'm cluing in to what you and Ville are saying. When referring to the relationship to N4014, you are not actually referring to the actual content of the paper (or the note on why it was rejected), but rather to what I assume is a discussion that went on behind the scenes. That discussion was not just on the specific issue (of explicitness), but a broader discussion about protecting the purity of separation between value types and non-value types.
I never listened to any other discussion on N4014. Value vs. non-value isn’t black and white. I can’t speak for Ville. But yes, it’s a broader discussion. One of the more obvious artifacts was a debate over whether explicit constructors should be invoked by return statements, which generated about a half-dozen papers.
And I gather what you're saying is that you want the equals sign to be the line of separation between value types and non-value types - that is, when you're using an equal sign, you must be working with a value type.
Does that correctly summarize what you and Ville are getting at?
atomic<int> x = {};
atomic<int> x = atomic<int>{};
Because the second example isn’t equivalent to the first. It’s equivalent to this:

atomic<int > x = { 0 };

The RHS of = is supposed to be a value. That’s why the conversion implemented by the copy and move constructors is “lvalue-to-rvalue.” This applies equally to initialization and assignment.

If atomic’s conversion constructor from its initial value were explicit, then I think the default constructor would be as well.

Arguably this should work too:

atomic< int > x = 0;

I do think it’s a bit silly to convert to a temporary before initializing the named variable, but I can’t comment further without digging into the history of the language.
So logically that means that the first statement is not really constructing an atomic on the right - it's (in essence) assigning (which is really a conversion construction) the (empty) list object on the right to the atomic on the left.
But if that's so, then why doesn't it work with types with an explicit default constructor?
The fact that it fails when the constructor is explicit implies that there must be (implicitly) an atomic<int> construction going on on the right. I mean, you can't sanely argue that whatever is being constructed on the left isn't explicitly an atomic<int>, right?
On the right, just a plain int. If it were really atomic, then special semantics would apply to evaluating it for the sake of making a copy. (More so for non-lock-free types.)

True, you’re proposing to guarantee elision of that copy. But copy elision was never intended as a pillar of the language. Things are supposed to work without it; it’s not a failsafe bypass around the lvalue-to-rvalue conversion.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Mark A. Gibbs
2015-09-12 01:48:05 UTC
Permalink
Post by David Krauss
I didn’t say not to experiment, I said that you get into trouble when you
lose value semantics. The problem with the example is that its “experiment”
just seems to be a random jumble of keywords, and then you say that the
result is surprising.
That *is* saying "don't experiment", because experimenting means trying
things that you don't know whether they'll work or not, and learning from
the discovery of what happens. Okay, perhaps you're not saying "don't
experiment *ever*", but you *are* saying "don't experiment with non-value
types". Of course, the catch is that the beginner won't know that rule, and
even if they do they won't know what non-value types are, so they won't
know where they can or can't experiment. So, basically, "don't experiment".

Anyway, if it's just that you think the experimentation example wasn't
realistic, I can easily provide you with a realistic one. I've seen it
happen plenty of times.
Post by David Krauss
An experiment is something that tests a hypothesis, and the example never
said what the student was after.
Now you're just trying to be pedantic, but I can do that, too. A *science*
experiment is one that involves hypothesis testing, but even then not all
science experiments involve hypotheses: there is such a thing as
"exploratory data analysis". Experimentation in the general sense is merely
"trying shit to see what happens". The latter type may be non-rigorous, but
it is very often the first stage in an investigation that will be followed
up by the former type. Great scientific discoveries often start with "hm, I
wonder what happens if...".

(Also, in the original example, there *was* a hypothesis being tested.
Specifically, that one of the four special member functions copy/move
construct/assign was being used in that initialization statement. The
experiment was putting markers in the four functions to reveal which. You
could consider that a "closed testing procedure" that tests multiple
hypotheses - in this case, 4, each with its own null hypothesis -
simultaneously.)
Post by David Krauss
In this case, the question would be, “why doesn’t the equals sign work”?
Actually, the question is not "why doesn't the equal sign work", because "atomic<int>
a = {};" works but "atomic<int> a = atomic<int>{};" doesn't. Clearly the
equal sign does work. The question is why doesn't it work when I write the
type twice, even though the braces on the right appear to be implying a
default constructed atomic on the right that is being "put" in the atomic
on the left - or aliased by the variable being declared on the left. And
this behaviour appears to be what that construct implies, because that's
what apparently happens according to "atomic<int> a = 0;" (doesn't work
because non-copy/non-movable) and "tuple<int> t = {0};" (doesn't work
because construction on the right is implicit).

I suggest that you are making a mistake by trying to interpret "type x =
type{...};" as an assignment between two values. Firstly, that seems a
suspicious argument right from the get go, because you don't have two
values. In fact, you have none before, then one after. And unlike all other
forms of initialization, in the interim you have two objects that were
constructed out of whole cloth right there in the statement, unobservably
(because of elision). (This is different from the cases of "type x = val;"
and "type x = func_yielding_rvalue();", because in both those two cases,
the RHS value is constructed elsewhere (though possibly elided).) The
argument that you really, really want this ghost object that never really
exists seems weak.

Secondly, the language refutes that interpretation. If a non-copy/non-move
type has a constructor that takes an int, but no assignment operators that
take an int, this still works: "type x = {0};". How can that be if this is
actually a transfer of values? The type has no way to take an int (or an
initializer list) except by construction, so it must therefore be
constructing on the right... but that can't be, because the type is
non-copy/non-movable, so if it were constructed on the right, it can't
possibly be transferred to the left. Therefore there cannot be two values
in that statement - there must only be one, the object on the right
constructed with the int on the left. (Of course, the language then turns
around and screws you again, if that constructor is explicit. As I keep
saying, I think you're barking up the wrong tree trying to argue for
consistency in construction statements. That ship has sailed.)
Post by David Krauss
And the answer would be, “the right-hand side doesn’t behave as a value.”
Yet this works:
auto&& x = std::atomic<int>{};

So clearly the claim that "=" only works with value types just doesn't hold
water.

"=" may be the dividing line between value types and non-value types in
general code - when it is actually being used as an assignment. But in the
context of initialization, it isn't, at least not consistently.

The flip side of regular syntax with loose semantic constraints is getting
Post by David Krauss
unexpected meaning from templates. Some things aren’t *supposed* to
compile.
But as I pointed out, that argument doesn't really apply to declarations of
the form "type x = type{...};". *Nobody* writes code like that with the
expectation that it is the only thing keeping non-copy/non-move types out
the door. Almost 20 years of C++ and 10 years of teaching it, I have never
seen that done (and I've seen some crazy shit). Anybody who does that is
being "clever" to the point of frustrating future maintenance of the code,
so there is no reason the standard should have to bless that behaviour.
There are better ways to prevent generic code from compiling in the face of
a non-value type. We just don't need this particular case.

Also, I'm not proposing "loose" semantic constraints. I'm proposing very
rigorous constraints... just constraints that are different from the
existing ones in one specific context (which are specious).

You're making it sound like I'm proposing throwing all distinction between
value and non-value types out - or perhaps removing the ability for "=" to
distinguish in all cases. That's not so. I'm talking about taking one...
specific... instance - just "type x = type{...};", and not even "type x =
val;" or "type x = func();", those should still fail in the absence of
copy/move ops - in a context where there are already lots of special cases
and exceptions (initialization), and for the gain of ending the madness of
having no way to construct a new object that works for all types. I think
we can let this one go.

There’s a language for that: Java. (Except, scalars in Java are new objects
Post by David Krauss
without using the new keyword.)
I realize you're invoking Java as the exemplar of a language that prefers
simplicity and consistency at the expense of flexibility, expressiveness
and power... exactly what we don't want C++ to become. But frankly, if a
bad language has a good idea... I'm a-gonna take it. I don't care if the
rest of the design in that language is misguided - if it has something that
works well, and it can be done in C++ without sacrificing anything
important... yeah, I'll steal from Java. I don't have so much pride that I
would refuse a good idea just because of its source.

Assuming so, I have to disagree with the reasoning. I don't disagree with
Post by David Krauss
maintaining a bright line of separation between value types and non-value
types (let's just go with VTs and NVTs) - what I disagree with is that the
equal sign is worth defending for this cause. I would say that battle is
atomic<int> x = {};
atomic<int> x = atomic<int>{};
Because the second example isn’t equivalent to the first.
I didn't claim they were equivalent. In fact, I obviously realize they
aren't because the whole point of my proposal is to *make* them equivalent.

I pointed out that the claim that "=" implies value semantics is false.
Atomics don't have value semantics, yet you can do "atomic<int> a = {};".

If it were really true that "=" really did imply value semantics in all
cases, then I would have to acquiesce - you would be right, and maintaining
a clear wall distinction between value types and non-value types is more
important than convenience. But it doesn't always imply value semantics.
There are already exceptions and gotchas... *especially* in the context I'm
focused on. Since the wall is already full of holes, the argument for
adding one more that makes things really convenient and consistent suddenly
has merit.
Post by David Krauss
atomic< int > x = 0;
I do think it’s a bit silly to convert to a temporary before initializing
the named variable, but I can’t comment further without digging into the
history of the language.
So why shouldn't this?:
atomic<int> a = atomic<int>{0};

What you're saying is that you think that it's reasonable that "atomic<int>
a = 0;" should be interpreted as "atomic<int> a{0};", and not "atomic<int>
a = atomic<int>{0};" (as it currently is). I agree - I see no sane reason
why it shouldn't. It's obvious what you really mean, and there's really no
reason a temporary has to be constructed then transferred to the named
variable.

So if that's reasonable, why is interpreting "atomic<int> a =
atomic<int>{0};" as "atomic<int> a{0};" so crazy? It's the exact... same...
reasoning: It's obvious what you really mean, and there's really no reason
a temporary has to be constructed then transferred to the named variable.

Remember, "atomic<int> a = {0};" is already interpreted as "atomic<int>
a{0};". "{0}" just by itself is an initializer_list<int>, yet atomic<int>
has no initializer_list<int> constructor, so that has to be what the
interpretation of what's happening is - the "{0}" is not "an initializer
list", but rather "the initializer list for the atomic being constructed".
This is already a context were the "normal" rules of "=" just don't apply.
What I'm proposing is basically just interpreting "atomic<int> a =
atomic<int>{0};" as "atomic<int> a atomic<int>{0};", then dropping the
redundant repetition of the type. (But, of course, ONLY in the case where
the type actually is repeated.)
Post by David Krauss
True, you’re proposing to guarantee elision of that copy. But copy elision
was never intended as a pillar of the language. Things are supposed to work
without it; it’s not a failsafe bypass around the lvalue-to-rvalue
conversion.
Yes, and I am proposing to change that - in this one, specific syntactic
construct. That's why elision is mentioned in the topic.

You're right, the current state of affairs is that elision is optional in
all cases, even in the case that I'm focused on. My argument is if we
change that rule just in this specific case, to make the elision
effectively required - and "required" to the point that there is no moving
at all, even conceptually - we gain:

- guaranteed efficiency (as opposed to almost-certain but not required
efficiency)
- and a practical solution to the initialization madness that works in
all cases (though may not do precisely what you specifically need in a
specific case, in which case you still have the other options).

The only purposes behind keeping the requirement that the type be
copy/movable are either a) to maintain language purity wrt the distinction
of value/non-value types, or b) because it is a practical way to actually
distinguish between value/non-value types in generic code.

For (a), I say that goal is already a lost cause, in this context. There
are so many peculiarities in the interpretations of the various
construction syntaxes, that this doesn't really seem the right place to be
adamant about drawing this line.

For (b), I say: nobody does it for that purpose, nobody would understand
that's what the intention was if it was actually used for that purpose,
there are several other - better - ways of achieving the same goal, and
finally, it just don't work anyway. There are more requirements to having
value semantics than just being copy/movable, so a type that does not have
value semantics but is copy/movable would pass the bar.

Given that the syntax doesn't actually distinguish anything but whether the
type is non-copy/non-movable or not - it doesn't actually serve the purpose
of detecting value semantics - and that construction in general is already
a context where some things that look like copies/moves are not actually, I
think we can get away with repurposing it, and gaining clarity and sanity
in one of the most basic and important parts of the language: constructing
objects.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
David Krauss
2015-09-12 05:31:33 UTC
Permalink
Post by David Krauss
I didn’t say not to experiment, I said that you get into trouble when you lose value semantics. The problem with the example is that its “experiment” just seems to be a random jumble of keywords, and then you say that the result is surprising.
That is saying "don't experiment", because experimenting means trying things that you don't know whether they'll work or not,
How can you say whether something “worked” if there was no goal in the first place? You only mentioned "Wanting to explore constructors and destructors,” so from that perspective, the experiment was a resounding success.
Post by David Krauss
and learning from the discovery of what happens. Okay, perhaps you're not saying "don't experiment ever", but you are saying "don't experiment with non-value types". Of course, the catch is that the beginner won't know that rule, and even if they do they won't know what non-value types are, so they won't know where they can or can't experiment. So, basically, "don't experiment”.
Don’t experiment unless you’re willing to discover something new. Don’t try to work in a vacuum, but maintain contact with the community of folks with related experience. These rules apply to all academia. Especially now, StackOverflow is always just a click away.

The confusion of the student in the second part of your example was due to copy elision, by the way, which isn’t guaranteed. Compilers still implement -fno-elide-copies last I checked. Although in your example, elision can effectively be guaranteed because the initializer is a prvalue, in other cases the move constructor is still really used. To cover all the bases, you’d need to subdivide copy-initialization into more different cases. It would not ultimately be a simplification, you’d just be sweeping a few things further under the rug.
Post by David Krauss
Anyway, if it's just that you think the experimentation example wasn't realistic, I can easily provide you with a realistic one. I've seen it happen plenty of times.
Sure, fire away.
Post by David Krauss
An experiment is something that tests a hypothesis, and the example never said what the student was after.
Now you're just trying to be pedantic, but I can do that, too. A science experiment is one that involves hypothesis testing, but even then not all science experiments involve hypotheses: there is such a thing as "exploratory data analysis". Experimentation in the general sense is merely "trying shit to see what happens". The latter type may be non-rigorous, but it is very often the first stage in an investigation that will be followed up by the former type. Great scientific discoveries often start with "hm, I wonder what happens if
".
If the student is only trying to learn the rules, then they’re not allowed to be upset when they find one.

I also recall learning about copy elision when copy constructor side effects disappeared. Even back then in the 90’s, there was literature to connect the surprising result to class value semantics, and turn the “exploratory data analysis” into a valuable learning experience.

Copy elision isn’t remotely new, nor is the element of surprise specific to non-value-semantic classes.
Post by David Krauss
(Also, in the original example, there was a hypothesis being tested. Specifically, that one of the four special member functions copy/move construct/assign was being used in that initialization statement. The experiment was putting markers in the four functions to reveal which. You could consider that a "closed testing procedure" that tests multiple hypotheses - in this case, 4, each with its own null hypothesis - simultaneously.)
Another platform or another similar experiment would have resulted in the move constructor being called.
Post by David Krauss
In this case, the question would be, “why doesn’t the equals sign work”?
Actually, the question is not "why doesn't the equal sign work", because "atomic<int> a = {};" works but "atomic<int> a = atomic<int>{};" doesn't. Clearly the equal sign does work. The question is why doesn't it work when I write the type twice, even though the braces on the right appear to be implying a default constructed atomic on the right
False supposition.
Post by David Krauss
that is being "put" in the atomic on the left - or aliased by the variable being declared on the left. And this behaviour appears to be what that construct implies, because that's what apparently happens according to "atomic<int> a = 0;”
I’ll grant that atomic<int> a = 0 should work, but only because 0 is not an atomic. If you use {0}, there is no move (or elision) needed.
Post by David Krauss
(doesn't work because non-copy/non-movable) and "tuple<int> t = {0};" (doesn't work because construction on the right is implicit).
This is already fixed.
Post by David Krauss
I suggest that you are making a mistake by trying to interpret "type x = type{...};" as an assignment between two values. Firstly, that seems a suspicious argument right from the get go, because you don't have two values. In fact, you have none before, then one after.
The expression type{
}, like any expression, has a value. You want to use it as the initial value of a persistent object, but in the first place it exists as a value, regardless of its enclosing context.

Your model doesn’t seem to accommodate type x = derived_type{};
Post by David Krauss
And unlike all other forms of initialization, in the interim you have two objects that were constructed out of whole cloth right there in the statement, unobservably (because of elision). (This is different from the cases of "type x = val;" and "type x = func_yielding_rvalue();", because in both those two cases, the RHS value is constructed elsewhere (though possibly elided).) The argument that you really, really want this ghost object that never really exists seems weak.
Secondly, the language refutes that interpretation. If a non-copy/non-move type has a constructor that takes an int, but no assignment operators that take an int, this still works: "type x = {0};”.
What do assignment operators have to do with this?
Post by David Krauss
How can that be if this is actually a transfer of values? The type has no way to take an int (or an initializer list) except by construction, so it must therefore be constructing on the right... but that can't be, because the type is non-copy/non-movable, so if it were constructed on the right, it can't possibly be transferred to the left.
An int is constructed on the right and transferred to the left. Easy peasy.
Post by David Krauss
Therefore there cannot be two values in that statement - there must only be one, the object on the right constructed with the int on the left. (Of course, the language then turns around and screws you again, if that constructor is explicit. As I keep saying, I think you're barking up the wrong tree trying to argue for consistency in construction statements. That ship has sailed.)
There’s plenty of literature on what explicit means, due to the recent return statement debate. Please review, for the sake of your students.
Post by David Krauss
And the answer would be, “the right-hand side doesn’t behave as a value.”
auto&& x = std::atomic<int>{};
So clearly the claim that "=" only works with value types just doesn't hold water.
"=" may be the dividing line between value types and non-value types in general code - when it is actually being used as an assignment. But in the context of initialization, it isn't, at least not consistently.
This is temporary lifetime extension. Is it impure to use = for reference binding? Interesting thought. However, the easy answer is that the value on the RHS is exactly preserved by reference binding, overriding class semantics. All expressions have values, after all, and “non-value-semantic” classes are only ones which may misbehave when some mathematical abstraction is assumed. “Value semantic” refers to the notion of separating a value from an object, not the existence of a value in the first place.

Reference binding gives expression templates conniptions: They use non-value types to represent mathematical values.

A reference stores an lvalue, i.e. an alias to an object which must remain valid — I’m content to leave it at that. (Well, not exactly satisfied
 the language should do more validation, and user-defined types should have access to lifetime extension as well, hence my proposal bit.ly/genlife <http://bit.ly/genlife>.)
Post by David Krauss
The flip side of regular syntax with loose semantic constraints is getting unexpected meaning from templates. Some things aren’t supposed to compile.
But as I pointed out, that argument doesn't really apply to declarations of the form "type x = type{...};". Nobody writes code like that with the expectation that it is the only thing keeping non-copy/non-move types out the door. Almost 20 years of C++ and 10 years of teaching it,
Nobody in their right mind would write type on both sides of the equals sign before C++11. We’re both limited to 4-5 years experience with this.
Post by David Krauss
I have never seen that done (and I've seen some crazy shit). Anybody who does that is being "clever" to the point of frustrating future maintenance of the code, so there is no reason the standard should have to bless that behaviour. There are better ways to prevent generic code from compiling in the face of a non-value type. We just don't need this particular case.
Also, I'm not proposing "loose" semantic constraints. I'm proposing very rigorous constraints... just constraints that are different from the existing ones in one specific context (which are specious).
To me, you sound specious. But I’m keeping an open mind; you’re the one pacing this discussion.
Post by David Krauss
You're making it sound like I'm proposing throwing all distinction between value and non-value types out - or perhaps removing the ability for "=" to distinguish in all cases. That's not so. I'm talking about taking one... specific... instance - just "type x = type{...};", and not even "type x = val;" or "type x = func();", those should still fail in the absence of copy/move ops - in a context where there are already lots of special cases and exceptions (initialization), and for the gain of ending the madness of having no way to construct a new object that works for all types. I think we can let this one go.
What about type x = type{ func() }?
Post by David Krauss
There’s a language for that: Java. (Except, scalars in Java are new objects without using the new keyword.)
I realize you're invoking Java as the exemplar of a language that prefers simplicity and consistency at the expense of flexibility, expressiveness and power... exactly what we don't want C++ to become. But frankly, if a bad language has a good idea... I'm a-gonna take it. I don't care if the rest of the design in that language is misguided - if it has something that works well, and it can be done in C++ without sacrificing anything important... yeah, I'll steal from Java. I don't have so much pride that I would refuse a good idea just because of its source.
Just as there’s C++-as-C, there’s also C++-as-Java — albeit with no garbage collector. (We should have a GC already, though!) Many instructors have really gone that route, to the chagrin of the rest of the community.
Post by David Krauss
I didn't claim they were equivalent. In fact, I obviously realize they aren't because the whole point of my proposal is to make them equivalent.
I pointed out that the claim that "=" implies value semantics is false. Atomics don't have value semantics, yet you can do "atomic<int> a = {};”.
Hmm, you deleted the part where I explained that is equivalent to atomic<int> a = {0}; and then re-asked the question.

A: atomic<int> a = {};
B: atomic<int> a = {0};
C: atomic<int> a = atomic<int>{};

A is equivalent to B. Not by language semantics, but because the class defines it that way.
A is not equivalent to C. You propose to make them equivalent at the language level.

What about classes for which B is not equivalent to C?
Post by David Krauss
If it were really true that "=" really did imply value semantics in all cases, then I would have to acquiesce - you would be right, and maintaining a clear wall distinction between value types and non-value types is more important than convenience. But it doesn't always imply value semantics. There are already exceptions and gotchas... especially in the context I'm focused on. Since the wall is already full of holes, the argument for adding one more that makes things really convenient and consistent suddenly has merit.
atomic< int > x = 0;
I do think it’s a bit silly to convert to a temporary before initializing the named variable, but I can’t comment further without digging into the history of the language.
atomic<int> a = atomic<int>{0};
What you're saying is that you think that it's reasonable that "atomic<int> a = 0;" should be interpreted as "atomic<int> a{0};”
No, I’m saying it’s reasonable to interpret it as atomic<int> a = {0} with braces and the equals sign.
Post by David Krauss
, and not "atomic<int> a = atomic<int>{0};" (as it currently is). I agree - I see no sane reason why it shouldn't. It's obvious what you really mean, and there's really no reason a temporary has to be constructed then transferred to the named variable.
So if that's reasonable, why is interpreting "atomic<int> a = atomic<int>{0};" as "atomic<int> a{0};" so crazy? It's the exact... same... reasoning: It's obvious what you really mean, and there's really no reason a temporary has to be constructed then transferred to the named variable.
No, because it should be possible to substitute a function, even returning a derived type, on the RHS.
Post by David Krauss
Remember, "atomic<int> a = {0};" is already interpreted as "atomic<int> a{0};”.
Not at the language level; those behave differently given an explicit constructor.
Post by David Krauss
"{0}" just by itself is an initializer_list<int>,
Not by a long shot.
Post by David Krauss
yet atomic<int> has no initializer_list<int> constructor, so that has to be what the interpretation of what's happening is - the "{0}" is not "an initializer list", but rather "the initializer list for the atomic being constructed". This is already a context were the "normal" rules of "=" just don't apply.
The normal rule of = is lvalue-to-rvalue conversion on the RHS. As far as I know, pre-conversion to a temporary of the initialized type is a mere historical artifact. It would be interesting to see a well-researched proposal to regularize = x initializers with = {x}. I’m sure the guys who drafted the copy-list-initialization spec thought about the difference, and such research should not overlook the generalized initializer list proposals, which are numerous and deep.
Post by David Krauss
What I'm proposing is basically just interpreting "atomic<int> a = atomic<int>{0};" as "atomic<int> a atomic<int>{0};", then dropping the redundant repetition of the type. (But, of course, ONLY in the case where the type actually is repeated.)
True, you’re proposing to guarantee elision of that copy. But copy elision was never intended as a pillar of the language. Things are supposed to work without it; it’s not a failsafe bypass around the lvalue-to-rvalue conversion.
Yes, and I am proposing to change that - in this one, specific syntactic construct. That's why elision is mentioned in the topic.
Applying it for class conversion expressions but not function calls: is that really going to improve teachability? Much less real-life usability?

Maybe it’s time to recognize the “almost” in AAA.
Post by David Krauss
guaranteed efficiency (as opposed to almost-certain but not required efficiency)
and a practical solution to the initialization madness that works in all cases (though may not do precisely what you specifically need in a specific case, in which case you still have the other options).
The only purposes behind keeping the requirement that the type be copy/movable are either a) to maintain language purity wrt the distinction of value/non-value types, or b) because it is a practical way to actually distinguish between value/non-value types in generic code.
For (a), I say that goal is already a lost cause, in this context. There are so many peculiarities in the interpretations of the various construction syntaxes, that this doesn't really seem the right place to be adamant about drawing this line.
You seem to be implicitly admitting that others have a broader perspective than yourself, but that you’re not willing to see the forest for the trees.

There’s no particular reason to expect AAA to work generically. It’s a recent invention since C++11, unrelated to earlier practice. Maybe a language can be designed around it, but a comprehensive retrofit to C++ is unlikely.

That said, given my accessor lifetime extension proposal and another one (not updated for Kona, but not dead) N4149 regarding expression template conversions, you could do

auto guard = make_guard();

wherein the “value” of a guard cannot be moved to a new object, but it can generate an accessor view to itself, leading to its self-preservation.
Post by David Krauss
For (b), I say: nobody does it for that purpose, nobody would understand that's what the intention was if it was actually used for that purpose, there are several other - better - ways of achieving the same goal, and finally, it just don't work anyway. There are more requirements to having value semantics than just being copy/movable, so a type that does not have value semantics but is copy/movable would pass the bar.
Value vs. non-value isn’t black and white. Not sure exactly what goal you’re supposing.

To be sure, a template over numeric types should not accept atomic<int>. It’s a facility for managing integers safely against multithreaded aliasing. It can store and retrieve an integer value, even up to implicitly converting both ways, but that doesn’t actually make it a number.

Sure, it takes time to teach that to a student. But I think that’s time well spent.
Post by David Krauss
Given that the syntax doesn't actually distinguish anything but whether the type is non-copy/non-movable or not - it doesn't actually serve the purpose of detecting value semantics - and that construction in general is already a context where some things that look like copies/moves are not actually, I think we can get away with repurposing it, and gaining clarity and sanity in one of the most basic and important parts of the language: constructing objects.
Maintaining sanity requires acknowledging subtle complexity.

It’s not too much to teach that 1) class value is the information that gets preserved by the lvalue-to-rvalue conversion, 2) as applied to the RHS of =, and 3) implemented by the copy and move constructors. This is not subtle; it should already be foundational knowledge. However, you can fairly substitute auto&& for auto as a fallback, and still call it AAA.

FWIW, to me, it’s a contortion to require that declarations should all begin with auto, and any resulting pain is deserved. If it hurts, stop doing it.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Tomasz
2015-09-11 04:43:49 UTC
Permalink
W dniu czwartek, 10 września 2015 01:27:18 UTC+2 uÅŒytkownik Mark A. Gibbs
Post by Mark A. Gibbs
But if you don't have a value, or if you do but you want to be explicit
auto v = type{val}; // or any other constructor arguments, including none
I feel sad when a people want to eliminate all the work that was done on
checking the units and the ratios on compile time, just to force the use of
shinny new auto. Please assume that I want to receive a duration in
seconds, and compare the results of:
std::chrono::seconds s = get_timeout();
auto s = std::chrono::seconds{s};
std::chrono::seconds s{get_timeout()};

To help out a bit, please consider following declarations:
int get_timeout(); //return number of seconds
std::chrono::seconds get_timeout();
And how the program will behave if one is replaced with another, or the
unit is changed.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Mark A. Gibbs
2015-09-11 06:12:14 UTC
Permalink
Post by Tomasz
W dniu czwartek, 10 września 2015 01:27:18 UTC+2 uÅŒytkownik Mark A. Gibbs
Post by Mark A. Gibbs
But if you don't have a value, or if you do but you want to be explicit
auto v = type{val}; // or any other constructor arguments, including none
I feel sad when a people want to eliminate all the work that was done on
checking the units and the ratios on compile time, just to force the use of
shinny new auto. Please assume that I want to receive a duration in
std::chrono::seconds s = get_timeout();
auto s = std::chrono::seconds{s};
std::chrono::seconds s{get_timeout()};
int get_timeout(); //return number of seconds
std::chrono::seconds get_timeout();
And how the program will behave if one is replaced with another, or the
unit is changed.
Could you please explain exactly what behaviour you think would be
surprising or problematic, with respect to the proposal in this topic?
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Tomasz
2015-09-11 08:00:52 UTC
Permalink
W dniu piątek, 11 września 2015 08:12:14 UTC+2 uÅŒytkownik Mark A. Gibbs
Post by Mark A. Gibbs
Post by Tomasz
W dniu czwartek, 10 września 2015 01:27:18 UTC+2 uÅŒytkownik Mark A. Gibbs
Post by Mark A. Gibbs
But if you don't have a value, or if you do but you want to be explicit
auto v = type{val}; // or any other constructor arguments, including none
I feel sad when a people want to eliminate all the work that was done on
checking the units and the ratios on compile time, just to force the use of
shinny new auto. Please assume that I want to receive a duration in
std::chrono::seconds s = get_timeout();
auto s = std::chrono::seconds{s};
std::chrono::seconds s{get_timeout()};
int get_timeout(); //return number of seconds
std::chrono::seconds get_timeout();
And how the program will behave if one is replaced with another, or the
unit is changed.
Could you please explain exactly what behaviour you think would be
surprising or problematic, with respect to the proposal in this topic?
The motivation behind proposed change is to allow "one proper"
initialization syntax for the case, when the user want to commit to a
specific type:
auto x = type{expr};
The example when the syntax are discussed usually present the situation
when the expression has type visible at the point of variable declaration.
Like constant in the blog example, but to see the difference and value of
having direct and copy intialization we need to discuss a example when type
of expr is not know or may be changed in the future - the cases when the
variable is initialized from a return of the function foo().

The difference between direct and non-direct initialization give us two
options:
Type x = foo(); //This will compile only if there is implicit conversion
between result of foo() and Type
Type x(foo()); //Will compile when there is explicit conversion from result
of foo() and Type
This is just other side of the coin, and allow to use the same conversion
to pass result from callee to caller, as in passing argument form caller to
callee.

Then there is a whole paradigm of having "meaningful types" that uses type
checking do differentiate between number of seconds and milliseconds or
between the weight of thing in newtons and mass in grams. Such "opaque"
class usually marks constructors that add semantics information (from bare
number to seconds) as explicit, so following will work:
std::chrono::seconds s = get_timeout();
Only if the get_timeout() will return a appropriate duration time. I am
using the compiler to check if the conversion is safe and I actually
getting the right number. But following:
std::chrono::seconds s(get_timeout());
Will work even if the get_timeout() is returning bare number. Now I am
taking resposibility for making sure that the number is actually number of
seconds not milliseconds.

Finally, returning to original topic. In case if we stick to use of the use
of ETTI only:
auto s = std::chrono::seconds(get_timeout());
The programmer has no longer way to express the fact that he wants only
implicit conversion to be invoked. This basically eliminates all the gain
that was achieved by meaningful types, but introducing unintended
conversion. I do think that teaching the initialization syntax that will
eliminate gains for unit/ratio classes is good direction.

Ironically the poeple that are promoting auto everywhere syntax are given
argument about lack of unintended conversion as argument for using it.
While use ETTI (auto x = type(expr)) acutally introduce possibility of more
bogus one. Just think of the Herb Sutter example:
auto x = unique_ptr<Base>{createDerived()};
It is working correctly in case when createDerived() is retruning
unique_ptr<Derived>. But what if it will start to return raw pointer to
class to object that has lifetime handled eslewhere? The syntax:
unique_ptr<Base> x = createDerived();
Will catch such change at compile time.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Mark A. Gibbs
2015-09-12 00:20:02 UTC
Permalink
Post by Tomasz
The difference between direct and non-direct initialization give us two
Type x = foo(); //This will compile only if there is implicit conversion
between result of foo() and Type
Type x(foo()); //Will compile when there is explicit conversion from
result of foo() and Type
What I'm proposing has no effect whatsoever on those two forms. You can
still use them, with no change in semantics. You're not losing any
flexibility. You're gaining a method that works for all types. It may not
work precisely the way you want for a specific case, but if you have
specific needs, go ahead and use one of the other forms. No one's tying
your hands.
Post by Tomasz
Ironically the poeple that are promoting auto everywhere syntax are given
argument about lack of unintended conversion as argument for using it.
While use ETTI (auto x = type(expr)) acutally introduce possibility of more
auto x = unique_ptr<Base>{createDerived()};
It is working correctly in case when createDerived() is retruning
unique_ptr<Derived>. But what if it will start to return raw pointer to
unique_ptr<Base> x = createDerived();
Will catch such change at compile time.
Okay, here's my counter example:
int get_count();

// Then later get_count() is changed to return a long. Or std::size_t.

int n = get_count(); // This will fail to detect the change. Bug reports of
// counts of -1234 objects ensue.

auto n = int{get_count()}; // This will detect the issue at compile time.

Now, as for my analysis of your example: When you change the interface
contract of a function, and either don't change the return type - or change
it to a type that is implicitly convertible to the original type in some
situations - I don't know how you can seriously expect to rely on the
language to save you from yourself.

No initialization syntax is perfect. But the difference between your
example and mine is that mine is a plausible update to a code base to
accommodate growth, whereas yours is really just a really dumb refactoring
decision that someone hoped that compiler would spare them from the
consequences of. The "auto" version catches the plausible modification, but
misses the terrible refactoring job. The non-"auto" version catches the
really dumb change that should never happen, but fails to catch the
plausible modification. Frankly, I'd prefer the "auto" result, in practice.
Oh, also, the "auto" version will crash the very first time the code that
puts the result of createDerived() in a unique_ptr gets called - basically
day 1 of testing - and debug mode will give you a message about the
double-delete... meanwhile the bug in the non-"auto" version could go for
years without being detected, unless and and until there happens to be a
case when the result of get_count() is too large, which may only happen
sporadically and be hard to reproduce. Advantage: auto.

Back to your example, relying on the semantics of "type x =
factory_function();" is a bad idea. Unless you put a comment next to it
explicitly explaining that that's what you intend, you risk someone coming
along and thinking that "type x{factory_function()};" is a better idea,
because it prevents narrowing, or something similar. The smart thing to do
is this:
template <typename T, typename U>
auto as(U&& u) -> T
{
return {u};
}

auto s = as<std::chrono::seconds>(get_timeout());
// or if you prefer a different name:
auto s = implicitly_as<std::chrono::seconds>(get_timeout());
auto s = safely_as<std::chrono::seconds>(get_timeout());

That will catch unwanted explicit conversions... and it will catch
narrowing (and if you don't want that, drop the braces)... *and* it is
self-documenting in code. And if you don't use the auto there, you have to
repeat the type, which is a bad idea. For auto, I think that's game, set,
and match.

Perhaps those of us who want to "force" everyone to use the "shiny new auto"
have given this a bit more thought than you give us credit for.

Anyway, to be clear, I am not proposing a way to initialize objects that
will solve every single possible problem imaginable. I am just proposing a
way that will work for every type, and do a sensible thing in all cases. It
may not work precisely the way you need it to for your specific instance,
in which case you can use a more specific form.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Tomasz
2015-09-12 08:43:43 UTC
Permalink
W dniu sobota, 12 września 2015 02:20:03 UTC+2 uÅŒytkownik Mark A. Gibbs
Post by Mark A. Gibbs
No initialization syntax is perfect. But the difference between your
example and mine is that mine is a plausible update to a code base to
accommodate growth, whereas yours is really just a really dumb refactoring
decision that someone hoped that compiler would spare them from the
consequences of. The "auto" version catches the plausible modification,
but misses the terrible refactoring job. The non-"auto" version catches
the really dumb change that should never happen, but fails to catch the
plausible modification. Frankly, I'd prefer the "auto" result, in
practice. Oh, also, the "auto" version will crash the very first time the
code that puts the result of createDerived() in a unique_ptr gets called
- basically day 1 of testing - and debug mode will give you a message about
the double-delete... meanwhile the bug in the non-"auto" version could go
for years without being detected, unless and and until there happens to be
a case when the result of get_count() is too large, which may only happen
sporadically and be hard to reproduce. Advantage: auto.
The same advantage applies for int x{get_count()} and this advantage
applies only for the build-in arithmetic types that allows numeric
conversion.

Back to your example, relying on the semantics of "type x =
Post by Mark A. Gibbs
factory_function();" is a bad idea. Unless you put a comment next to it
explicitly explaining that that's what you intend, you risk someone coming
along and thinking that "type x{factory_function()};" is a better idea,
because it prevents narrowing, or something similar. The smart thing to do
Again, such refactoring only give a gains for a small set of types, i.e.
built in arithmetic types, that allows narrowing conversion. The type x =
factory_function() prevents explicit conversion for every other type. That
is way I do not like pomotion of auto x = type{expr} syntax - the gain is
achieved for very small set of types, versus possible safety loss of others.
Post by Mark A. Gibbs
template <typename T, typename U>
auto as(U&& u) -> T
{
return {u};
}
auto s = as<std::chrono::seconds>(get_timeout());
auto s = implicitly_as<std::chrono::seconds>(get_timeout());
auto s = safely_as<std::chrono::seconds>(get_timeout());
That will catch unwanted explicit conversions... and it will catch
narrowing (and if you don't want that, drop the braces)... *and* it is
self-documenting in code. And if you don't use the auto there, you have
to repeat the type, which is a bad idea. For auto, I think that's game,
set, and match.
The new C++ cast was designed, they was intended to be long and ugly, to
give a visible hint that something may be wrong. You are currently
proposing that:
auto s = type{expr};
Syntax should be used for unsafe conversions. And:
auto s = as<std::chrono::seconds>(get_timeout());
auto s = implicitly_as<std::chrono::seconds>(get_timeout());
auto s = safely_as<std::chrono::seconds>(get_timeout());
For the one that was marked as safe by author of the class.
Post by Mark A. Gibbs
Perhaps those of us who want to "force" everyone to use the "shiny new
auto" have given this a bit more thought than you give us credit for.
Anyway, to be clear, I am not proposing a way to initialize objects that
will solve every single possible problem imaginable. I am just proposing a
way that will work for every type, and do a sensible thing in all cases. It
may not work precisely the way you need it to for your specific instance,
in which case you can use a more specific form.
I do not think that invoking conversion that was marked as unsafe by the
class author (marked explicit) is sensible default. And the avoidance of
narrowing conversion for small set of types, does not justify it.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Markus Grech
2015-09-12 14:19:53 UTC
Permalink
Uniform initialization was one of the biggest mistakes in the language to
date. I can therefore only agree with the idea to fix this issue to provide
a true uniform syntax. Please do not forget arrays however, something like
the following should work as well:
auto x = int[]{ 1, 2, 3 };
Not sure if this is doable without ambiguities.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Martin Molzer
2016-01-24 02:13:47 UTC
Permalink
Just want to post a question I asked on stackoverflow where a comment was
that my code wasn't future proof. It was about a class that could not be
created on the heap, relying on a deleted copy and move constructor and the
fact that you can capture such a variable in a temporary:

http://stackoverflow.com/questions/34948403/

This proposal would destroy that use case, if it'd go through and actually
open up a problem for me if there was no way to suppress the behaviour for
a specific class.

I have no proposition on how to tell the compiler that a specific class
would not allow copy/move elision but if it would, to be consistent, I'd
make it forbidden to do so, even in the cases that are currently allowed by
the standard. But there should be a way to forbid/restrict - see
private/protected move and copy constructors.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2016-01-24 19:52:40 UTC
Permalink
Post by Martin Molzer
Just want to post a question I asked on stackoverflow where a comment was
that my code wasn't future proof. It was about a class that could not be
created on the heap, relying on a deleted copy and move constructor and the
http://stackoverflow.com/questions/34948403/
This proposal would destroy that use case, if it'd go through and actually
open up a problem for me if there was no way to suppress the behaviour for
a specific class.
... and?

Here's the thing: C++ has no way to do what you want. And the language
features you are (ab)using to achieve it are not intended to provide that
functionality. So there's no reason we shouldn't have mandatory elision
just because it stops you from doing this.

It would be far better to propose a language feature that would actually
allow you to declare a class which can be forbidden from being heap
allocated. Everyone would be better off that way.

Also, it would allow users to stack allocate them and return them, through
forced elision, to whomever needed them within their scope. Thus making
stack-only classes more inherently useful. There could also be syntax for
storing them as NSDMs of other stack-only classes, again making it more
useful than your narrow use case.
Post by Martin Molzer
I have no proposition on how to tell the compiler that a specific class
would not allow copy/move elision but if it would, to be consistent, I'd
make it forbidden to do so, even in the cases that are currently allowed by
the standard. But there should be a way to forbid/restrict - see
private/protected move and copy constructors.
Forbidding or restricting what can and cannot be elided adds complexity for
little gain. Again, simply providing syntax to say that this class cannot
be heap allocated would be better.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-proposals/.
Vishal Oza
2016-01-24 20:10:24 UTC
Permalink
Post by Nicol Bolas
Post by Martin Molzer
Just want to post a question I asked on stackoverflow where a comment was
that my code wasn't future proof. It was about a class that could not be
created on the heap, relying on a deleted copy and move constructor and the
http://stackoverflow.com/questions/34948403/
This proposal would destroy that use case, if it'd go through and
actually open up a problem for me if there was no way to suppress the
behaviour for a specific class.
... and?
Here's the thing: C++ has no way to do what you want. And the language
features you are (ab)using to achieve it are not intended to provide that
functionality. So there's no reason we shouldn't have mandatory elision
just because it stops you from doing this.
It would be far better to propose a language feature that would actually
allow you to declare a class which can be forbidden from being heap
allocated. Everyone would be better off that way.
Also, it would allow users to stack allocate them and return them, through
forced elision, to whomever needed them within their scope. Thus making
stack-only classes more inherently useful. There could also be syntax for
storing them as NSDMs of other stack-only classes, again making it more
useful than your narrow use case.
Post by Martin Molzer
I have no proposition on how to tell the compiler that a specific class
would not allow copy/move elision but if it would, to be consistent, I'd
make it forbidden to do so, even in the cases that are currently allowed by
the standard. But there should be a way to forbid/restrict - see
private/protected move and copy constructors.
Forbidding or restricting what can and cannot be elided adds complexity
for little gain. Again, simply providing syntax to say that this class
cannot be heap allocated would be better.
would your idea be something like this:
class foo stackonly
{
//
};
where the keyword stackonly tell the compiler to not ever use new or delete
to allocate the object?
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-proposals/.
Patrice Roy
2016-01-25 01:10:59 UTC
Permalink
Wouldn't that choice of words imply introducing the very concept of a stack
in the standard? As far as I know, it's not there currently (it's a good
thing in some ways, and an annoyance in others).

Just asking...
Post by Vishal Oza
Post by Nicol Bolas
Post by Martin Molzer
Just want to post a question I asked on stackoverflow where a comment
was that my code wasn't future proof. It was about a class that could not
be created on the heap, relying on a deleted copy and move constructor and
http://stackoverflow.com/questions/34948403/
This proposal would destroy that use case, if it'd go through and
actually open up a problem for me if there was no way to suppress the
behaviour for a specific class.
... and?
Here's the thing: C++ has no way to do what you want. And the language
features you are (ab)using to achieve it are not intended to provide that
functionality. So there's no reason we shouldn't have mandatory elision
just because it stops you from doing this.
It would be far better to propose a language feature that would actually
allow you to declare a class which can be forbidden from being heap
allocated. Everyone would be better off that way.
Also, it would allow users to stack allocate them and return them,
through forced elision, to whomever needed them within their scope. Thus
making stack-only classes more inherently useful. There could also be
syntax for storing them as NSDMs of other stack-only classes, again making
it more useful than your narrow use case.
Post by Martin Molzer
I have no proposition on how to tell the compiler that a specific class
would not allow copy/move elision but if it would, to be consistent, I'd
make it forbidden to do so, even in the cases that are currently allowed by
the standard. But there should be a way to forbid/restrict - see
private/protected move and copy constructors.
Forbidding or restricting what can and cannot be elided adds complexity
for little gain. Again, simply providing syntax to say that this class
cannot be heap allocated would be better.
class foo stackonly
{
//
};
where the keyword stackonly tell the compiler to not ever use new or
delete to allocate the object?
--
---
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
Visit this group at
https://groups.google.com/a/isocpp.org/group/std-proposals/.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2016-01-25 02:14:16 UTC
Permalink
Post by Patrice Roy
Wouldn't that choice of words imply introducing the very concept of a
stack in the standard? As far as I know, it's not there currently (it's a
good thing in some ways, and an annoyance in others).
We're not making a formal proposal; we know what we're talking about, so
using loose language is fine.

My point was simply that his backdoor method for making such types would be
better handled by an explicit feature, not by gimping a genuinely useful
feature like guaranteed elision.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-proposals/.
Patrice Roy
2016-01-25 02:28:03 UTC
Permalink
Cool; there's something there, though, which explains my enquiry.

For threads, there's an actual need for offering a way to specify the
required stack size at construction time (this cannot be achieved with most
operating systems once the thread has been created, which excludes using
native_handle), so we'll have to work something on that side for some
(important) specialized users.

It's not the subject of this discussion, so I did not want to pollute the
exchanges with this topic (peripheral for the moment), but should the
relation-to-stack issue remain relevant, we might have cross-concerns here.

Cheers!
Post by Nicol Bolas
Post by Patrice Roy
Wouldn't that choice of words imply introducing the very concept of a
stack in the standard? As far as I know, it's not there currently (it's a
good thing in some ways, and an annoyance in others).
We're not making a formal proposal; we know what we're talking about, so
using loose language is fine.
My point was simply that his backdoor method for making such types would
be better handled by an explicit feature, not by gimping a genuinely useful
feature like guaranteed elision.
--
---
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
Visit this group at
https://groups.google.com/a/isocpp.org/group/std-proposals/.
--
---
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-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-proposals/.
Loading...