Hi I hope I am not violating any policy or best practice. My question naturally belongs to the original thread:
https://groups.google.com/a/isocpp.org/d/topic/std-proposals/cXneqUj-5oo/discussion
But it was getting so long and with so many branches, that even my browser started having problems parsing it.
It looks like the interface for initializing optional objects starts to settle on something similar to the original proposal:
1) optional<Guard> o1 = none; // disengaged
2) optional<Guard> o2; // same as above
3) optional<Guard> o3 = {}; // same as above
optional<Guard> o4{emplace, 2, "2"}; // calls Guard{2, "2"} in-place
optional<Guard> o5{emplace}; // calls Guard{} in-place.
We also agree that we do not want to allow any copy initialization from initialization list to avoid the confusion with the new default initialization syntax ={} (see o3 in the example).
But I would still find it useful if optional provided some sort of "converting constructor" so that the following would be possible:
int i = 1;
optional<int> oi = i;
optional<long> ol = i;
optional<string> os = "hello";
optional<char> oc = 'c';
optional<T>::optional(T&&);
optional<T>::optional(const T&);
Are not good candidates, because they would allow the following initialization:
Record john{"John", 5500};
Record mary{"Mary", 5500};
Record anonymous{};
optional<Record> john = {"John", 5500};
optional<Record> mary = {"John"};
optional<Record> anonymous = {}; // BUG!
Here again it might be surprising that initialization list is perfect forwarded except for the empty list case.
So apart from constructors:
1) default ctor
2) copy ctor
3) move ctor
4) optional<T>::optional(none_t);
5) explicit template <class ...Args> optional<T>::optional(emplace_t, Args&&,,, args);
We could add a converting constructor:
template <class U> requires Convertible<U, T>
optional<T>::optional(U&& v);
The part "requires Convertible<U, T>" only means that the constructor will be sfinaed-away if U is not convertible to T.
The addition of this constructor would still cover many refactoring cases of "optionalizing function parameters":
// before refactoring:
void fun(string s);
fun("hello");
// after refactoring
void fun(optional<string> os);
fun("hello"); // still compiles
On Tuesday, June 26, 2012 7:32:23 AM UTC+10, Andrzej Krzemieński wrote:Hi I hope I am not violating any policy or best practice. My question naturally belongs to the original thread:
https://groups.google.com/a/isocpp.org/d/topic/std-proposals/cXneqUj-5oo/discussion
But it was getting so long and with so many branches, that even my browser started having problems parsing it.
That was my impression as well as my poor old head "started having problems parsing it". So, I personally most welcome this idea of focusing on the construction interface.
It looks like the interface for initializing optional objects starts to settle on something similar to the original proposal:
1) optional<Guard> o1 = none; // disengaged
2) optional<Guard> o2; // same as above
3) optional<Guard> o3 = {}; // same as above
Please forgive my ignorance (I have not had any experience with {} initialization style even though I read and watched quite a bit about it). To the compiler #3 is the same as #2, right? That is the reason #3 *has* to be together with #2... because it's the same thing, right?
optional<Guard> o4{emplace, 2, "2"}; // calls Guard{2, "2"} in-place
optional<Guard> o5{emplace}; // calls Guard{} in-place.
If I understand this new {} initialization style, then it can also be written as
optional<Guard> o4 = {emplace, 2, "2"}; // calls Guard{2, "2"} in-place
optional<Guard> o5 = {emplace}; // calls Guard{} in-place.
Is it correct?
We also agree that we do not want to allow any copy initialization from initialization list to avoid the confusion with the new default initialization syntax ={} (see o3 in the example).
But I would still find it useful if optional provided some sort of "converting constructor" so that the following would be possible:
int i = 1;
optional<int> oi = i;
optional<long> ol = i;
optional<string> os = "hello";
optional<char> oc = 'c';
Yes, I admit that this API looks very attractive to me as well. Unfortunately, (if my memory serves me), this API can only be guaranteed to be efficient if it deploys perfect forwarding (we can't expect everyone having move cnstr). Without PF (and move cnstr) it has to copy which might be quite expensive. And PF did not quite cut it as it could not be perfect, right?
So, we had to settle (if I am not mistaken) on
optional<int> oi = { emplace, i };
optional<long> ol = { emplace, i };
optional<string> os = { emplace, "hello" };
optional<char> oc = { emplace, 'c' };
which although wordier but has the beauty (in my eyes anyway) of being explicit about what it is doing.
Constructors with this signature:
optional<T>::optional(T&&);
optional<T>::optional(const T&);
Are not good candidates, because they would allow the following initialization:
Record john{"John", 5500};
Record mary{"Mary", 5500};
Record anonymous{};
optional<Record> john = {"John", 5500};
optional<Record> mary = {"John"};
optional<Record> anonymous = {}; // BUG!
Here again it might be surprising that initialization list is perfect forwarded except for the empty list case.
Bummer, I can't help it but this "optional<Record> anonymous = {}" reads to me -- create optional with an Record::Record().
On Tue, Jun 26, 2012 at 6:06 PM, Ville Voutilainen wrote:
> On 26 June 2012 10:55, Andrzej Krzemieński wrote:
> ...
>>> optional<Guard> o4 = {emplace, 2, "2"}; // calls Guard{2, "2"} in-place
>>> optional<Guard> o5 = {emplace}; // calls Guard{} in-place.
>>> Is it correct?
>> Yes, with possibly one difference (of which I am uncertain right now), that
>> your example may require a MoveConstructability (even if the move is
>> elided).
>
> Correct. Foo x = whatever (whatever being a value or a brace-init list)
> is copy-initialization, so regardless of elision the copy/move must be
> present and accessible and non-explicit.
Ville, thanks for the explanations. Much appreciated. Just a
(hopefully) quick clarification to avoid making a fool of myself later
and to avoid veering away from the main topic. So,
1) Foo foo = { 1 };
2) Foo foo = 1;
are exactly the same, right? #2 has always required the copy cnstr and
non-explicit Foo(int) even though the copying was optimized away. If
so, then in
Foo h = false;
This can be implemented something like:
template< class U, bool ignore = enable_if<convertible<U, T>>::value >
optional<T>::optional(U&& u)
{
new (getStorage()) T(std::forward<U>(u));
engaged = true;
}
And it does not even require that T is moveable.
// before refactoring:
void fun(string s);
fun("hello");
// after refactoring
void fun(optional<string> os);
fun("hello"); // still compiles
// before refactoring:
void fun(string s);
fun("hello");
// after refactoring
void fun(optional<string> os);
fun("hello"); // still compiles
What happens if, instead of doing the above refactoring, I just decide to add an overload:
void fun(string s);
void fun(optional<string> os);
fun("hello");
I'd really like it to still call fun(string).
>> // before refactoring:
>> void fun(string s);
>> fun("hello");
>> // after refactoring
>> void fun(optional<string> os);
>> fun("hello"); // still compiles
> What happens if, instead of doing the above refactoring, I just decide to
> add an overload:
> void fun(string s);
> void fun(optional<string> os);
> fun("hello");
> I'd really like it to still call fun(string).
+1. I don't want implicit conversions from the underlying datatype to optional.
// before refactoring:
void fun(string s);
fun("hello");
// after refactoring
void fun(optional<string> os);
fun("hello"); // still compiles
What happens if, instead of doing the above refactoring, I just decide to add an overload:
void fun(string s);
void fun(optional<string> os);
fun("hello");
I'd really like it to still call fun(string).-- would anyone really want to have these two overloads?
void fun(string s);
void fun(optional<string> os);
Aren't they almost the same thing? Does having the two not indicate some design error?
void fun(string s);
void fun(optional<string> os);
fun("hello");
I'd really like it to still call fun(string).
Would the following not suit your purpose better?
void fun(Expensive const& e = CreateExpensive());
--
> I'd really like it to still call fun(string).
+1. I don't want implicit conversions from the underlying datatype to optional.
Really? This makes a huge difference for the design of Optional. I believe It will lose much in useability.
On Wednesday, June 27, 2012 11:42:18 AM UTC-3, Andrzej Krzemieński wrote:> I'd really like it to still call fun(string).
+1. I don't want implicit conversions from the underlying datatype to optional.Me neither.Really? This makes a huge difference for the design of Optional. I believe It will lose much in useability.Can you elaborate? Are you referring to the *converting* constructor?
If so, the end user can always use make_optional<string>("hello") if necessary, can't he?
// before refactoring:
void fun(string s);
fun("hello");
// after refactoring
void fun(optional<string> os);
fun("hello"); // still compiles
What happens if, instead of doing the above refactoring, I just decide to add an overload:
void fun(string s);
void fun(optional<string> os);
fun("hello");
I'd really like it to still call fun(string).
void fun(string s);
void fun(optional<string> os);
I am not familiar with this style of programming (heavily overloading function names), so pardon me if what I type below is unfair or ignorant. I am trying to understand.
So far, I understood, that since you do a lot of function overloads on "similar" types, implicit conversion is your foe because it unnecessarily causes ambiguities that would have not arisen if implicit conversions were banned altogether. It seams that you have a problem with implicit conversions in general, but since you have to accept them you fight to minimize their use in the Standard Library.
Am I right so far?
Assuming that I am, going to your string example:
void fun(string s);
You probably never use this function like this:
fun("hello");
I believe, the same applies to overloads:
void fun(Expensive const& e);if you pass objects of type Expensive, the first overload is chosen. But if you pass some type only convertible to Expensive, you already have a problem with your other overloads,
void fun(optional<Expensive> const& e);
Also, looking from a different angle if you are in the control of overloads, and your void fun(Expensive const& e) worked fine so far, and you want to overload on optional, and you anticipate implicit conversion problems, you can always add a third overload:
void fun(Expensive const& e);
void fun(optional<Expensive> const& e);
template <class T> requires Convertible<T, Expensive >
void fun(T&& e) {
fun(Expensive(e));
}
On Thu, Jun 28, 2012 at 4:45 PM, Andrzej Krzemieński <akrz...@gmail.com> wrote:I'm OK with implicitely constructing an optional<T> given a T, but I'm
not OK if I have an U which happens to be implicitly convertible to U:
optional<string> opt = string("hello") ; // OK
optional<string> opt = "hello" ; // not OK
With this, I find that ' optional<Record> john = {"John, 5500} ' doesn't
seem to work, in either g++ or clang. I don't see why it would.
I think you may have misunderstood me (or I have misunderstood you).
I am very happy for 'emplace' to be a feature of the proposal, and
should indeed be the only way of doing 'perfect forwarding'.
However, I disagreed with your wording "However, it is the preferred,
most explicit and unambiguous construction mechanism". I took this to
mean "We would advise users to use (for example)
optional<Record> o = {emplace, "hello", 1};
Rather than:
optional<Record> o = Record("hello", 1);
or
Record rec("hello", 1);
optional<Record> o = rec;
So in short (just to avoid confusion, mainly mine), I mainly agree with
the original proposal:
* Allow { emplace , .. } for in-place construction.
* Allow construction from T, and (while the standard of course doesn't
state such things), make this the 'recommended' interface.
* Make the default constructor produce an empty optional.
// Creating engaged optional
optional<int> opt10 = 1; // Calls opt(T&&)
optional<int> opt11 = {1}; // same as above
optional<int> opt12{1}; // same as above
optional<int> opt13 = k; // Calls opt(T const&)
optional<int> opt14 = {k}; // same as above
optional<int> opt15{k}; // same as above
optional<Foo> opt16 = Foo(); // Calls opt(T&&)
optional<Foo> opt17 = foo; // Calls opt(T const&)
optional<Foo> opt18 = {emplace, "", 1}; // Calls opt(emplace, ...)
// optional<Foo> opt19 = {emplace, 1, ""}; // Does not compile. No
matching Foo::Foo
// optional<Foo> opt19 = {"", 1}; // Does not compile. No matching opt cnstr
I was not able to figure out what templated constructors are needed
for as I did not find them mentioned in the proposal.
not OK if I have an U which happens to be implicitly convertible to U:
optional<string> opt = string("hello") ; // OK
optional<string> opt = "hello" ; // not OK
Tentative +1. I have to think about it a bit, but it feels right so far...