std::optional -- creating optional objects

411 views
Skip to first unread message

Andrzej Krzemieński

unread,
Jun 25, 2012, 5:32:23 PM6/25/12
to std-pr...@isocpp.org
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:

optional<Guard> o1 = none; // disengaged
optional<Guard> o2;      // same as above
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';

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

Do you find it acceptable and useful?

Regards,
&rzej

Vladimir Batov

unread,
Jun 25, 2012, 7:37:26 PM6/25/12
to std-pr...@isocpp.org
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().
 
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

Ah, having this for refactoring purposes would be nice... really nice. How does it work? Say, I have a Foo class with no move semantics.

class Foo { Foo(char const*); };
void fun(Foo const&);
fun("hello");

The above implicitly calls Foo(char const*).

void fun(optional<Foo> const&);

fun("hello"); // still compiles

Now Foo is implicitly constructed in-place, inside optional, right? If so, isn't it the same PF construction but only for one arg?


Andrzej Krzemieński

unread,
Jun 26, 2012, 3:55:45 AM6/26/12
to std-pr...@isocpp.org


W dniu wtorek, 26 czerwca 2012 01:37:26 UTC+2 użytkownik Vladimir Batov napisał:
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?

Yes -- according to my understanding. Even if some hypothetical type OptionalContainer has an "initializer-list constructor, when you initialize with an empty initializer list the default constructor (or more precisely: value initialization) is favored over the initializer list.

This is very tricky. You cannot see this nuance in case of STL container because the default-constructing the object means the same as initializing a zero-sized sequence. but for types like optional<vector> this nuance hurts. But there is a good reason to have the language work this way:

Say you have a constructor template <typename U> OptionalContainer(initializer_list<U> iu), and you initialize the object with an empty list:

OptionalContainer oc = {};

To what type should U be deduced?

I hesitate to say if #2 and #3 are identical, as I am not sure if #3 does not require the type to be move-constructible. I believe not, but I cannot find it anywhere in the wording.


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? 

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

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?

As you say below, this is a limited proposal of "1-argument perfect forwarding".
 
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().

This means that even in the current variant, where optional's TDC creates a disengaged object, the behavior will easily confuse people. I guess that the problem is in the language that uses ={} for value initialization.


Precisely so, and I like your name for it: one-arg perfect forwarding, or -- since we like acronyms -- "1APF".  The primary problem with "variadic" PF was the zero-argument case, and I believe 1APF does not suffer from most of the problems mentioned before.  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.

Ville Voutilainen

unread,
Jun 26, 2012, 4:06:03 AM6/26/12
to std-pr...@isocpp.org
On 26 June 2012 10:55, Andrzej Krzemieński <akrz...@gmail.com> wrote:
> Yes -- according to my understanding. Even if some hypothetical type
> OptionalContainer has an "initializer-list constructor, when you initialize
> with an empty initializer list the default constructor (or more precisely:
> value initialization) is favored over the initializer list.

Correct. That's in order to provide a generic way to do value initialization.

> Say you have a constructor template <typename U>
> OptionalContainer(initializer_list<U> iu), and you initialize the object
> with an empty list:
> OptionalContainer oc = {};
> To what type should U be deduced?

I don't see any way to deduce it there.

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

Andrzej Krzemieński

unread,
Jun 26, 2012, 4:20:55 AM6/26/12
to std-pr...@isocpp.org

And I probably should have mentioned that this technique has been suggested by Daniel Kruegler.

Vladimir Batov

unread,
Jun 26, 2012, 5:41:48 PM6/26/12
to std-pr...@isocpp.org
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

3) Foo foo1 = { 1 , "mama" };
4) Foo foo2 = Foo(1, "mama");
5) Foo foo3(1, "mama");

#3 is somewhere in-between #4 and #5 as #3 has the same requirements
as #4 but has the luxury of avoiding copying as #5.

Tnx,
V.

Ville Voutilainen

unread,
Jun 26, 2012, 5:49:06 PM6/26/12
to std-pr...@isocpp.org
On 27 June 2012 00:41, Vladimir Batov <vb.ma...@gmail.com> wrote:
> 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

Unfortunately they aren't exactly the same. If Foo has a constructor that
accepts an initializer_list<int>, that will work with #1 but not with #2.


> so, then in
> 3) Foo foo1 = { 1 , "mama" };
> 4) Foo foo2 = Foo(1, "mama");
> 5) Foo foo3(1, "mama");
> #3 is somewhere in-between #4 and #5 as #3 has the same requirements
> as #4 but has the luxury of avoiding copying as #5.

#3 doesn't have that luxury. It may avoid copying via elision, but the semantic
checking for an existing, accessible and non-explicit copy/move constructor
will still be performed. #5 is direct-initialization, so it doesn't require any
of those semantics.

Vladimir Batov

unread,
Jun 26, 2012, 5:53:44 PM6/26/12
to std-pr...@isocpp.org
On Tue, Jun 26, 2012 at 5:55 PM, Andrzej Krzemieński wrote:
> W dniu wtorek, 26 czerwca 2012 01:37:26 UTC+2 użytkownik Vladimir Batov
> napisał:
>> On Tuesday, June 26, 2012 7:32:23 AM UTC+10, Andrzej Krzemieński wrote:
>>> ...
Ah, I was "afraid" :-) so. If so, then people might want/expect the
same behavior for cases like:

// before refactoring
class Foo { Foo(int, char const*); };
void fun(Foo const&);
fun({ 1, "hello"});

If the above is a valid code, then I feel it needs to be handled as
the originally mentioned "fun("hello")" was.

V.

Vladimir Batov

unread,
Jun 26, 2012, 6:07:16 PM6/26/12
to std-pr...@isocpp.org
On Wed, Jun 27, 2012 at 7:49 AM, Ville Voutilainen wrote:
> On 27 June 2012 00:41, Vladimir Batov wrote:
>> 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
>
> Unfortunately they aren't exactly the same. If Foo has a constructor that
> accepts an initializer_list<int>, that will work with #1 but not with #2.

Jeez, it's all getting subtle and complicated. I thought they were
planning to make C++ more accessible and to bring it to the masses.
;-) Tnx, for the explanations. Seems like I *have* to spend my Sunday
going through the new standard. Just cursory reading obviously does
not cut it.

Johannes Schaub

unread,
Jun 26, 2012, 6:08:02 PM6/26/12
to std-pr...@isocpp.org


On Tuesday, June 26, 2012 11:41:48 PM UTC+2, Vladimir Batov wrote:
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


That's incorrect. Copy-list initialization does not inherently require a move or copy constructor. Since I don't know how Foo is defined (I haven't followed that long discussion), I will presume the following definition

    struct Foo { 
      Foo(int);    
      explicit Foo(bool);
      Foo(Foo const&) = delete;
    };


Then this is fine:

    Foo f = { 0 };
    Foo g(0);
    Foo h = false;

This is not:

    Foo i = 0; // copy/move ctor required
    Foo j = { false }; // use of explicit ctor

Ville Voutilainen

unread,
Jun 26, 2012, 6:14:56 PM6/26/12
to std-pr...@isocpp.org
On 27 June 2012 01:08, Johannes Schaub <schaub....@googlemail.com> wrote:
> That's incorrect. Copy-list initialization does not inherently require a
> move or copy constructor. Since I don't know how Foo is defined (I haven't

Oops, indeed.

> Then this is fine:
>     Foo f = { 0 };
>     Foo g(0);
>     Foo h = false;
> This is not:
>     Foo i = 0; // copy/move ctor required
>     Foo j = { false }; // use of explicit ctor

Yep.

Ville Voutilainen

unread,
Jun 26, 2012, 6:17:02 PM6/26/12
to std-pr...@isocpp.org
On 27 June 2012 01:14, Ville Voutilainen <ville.vo...@gmail.com> wrote:
>> Then this is fine:
>>     Foo f = { 0 };
>>     Foo g(0);
>>     Foo h = false;
>> This is not:
>>     Foo i = 0; // copy/move ctor required
>>     Foo j = { false }; // use of explicit ctor
> Yep.

..except that Foo h = false; isn't fine. That *is*
copy-initialization, not copy-list-initialization,
and it's ill-formed because of the deleted copy constructor. ;)

Johannes Schaub

unread,
Jun 26, 2012, 6:18:29 PM6/26/12
to std-pr...@isocpp.org
Indeed it is :)
 
    Foo h = false;


Oops. Of course this is invalid because of the missing copy/move constructor. What I wanted to show by this is: The explicit constructor is not considered and not used. Adjusted example for that:

    struct Bar { 
      Bar(int);
      explicit Bar(bool);
    };

    Bar b = false; // Fine!
    Bar j = { false }; // invalid: use of explicit ctor


Andrzej Krzemieński

unread,
Jun 27, 2012, 4:19:18 AM6/27/12
to std-pr...@isocpp.org


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.

That is a bit incorrect, it should read:

template <
  class U,
  typename enable_if<is_convertible<U, T>::value, bool>::type = false

Nevin Liber

unread,
Jun 27, 2012, 10:26:03 AM6/27/12
to std-pr...@isocpp.org
On 25 June 2012 16:32, Andrzej Krzemieński <akrz...@gmail.com> wrote:

// 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).
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Ville Voutilainen

unread,
Jun 27, 2012, 10:29:36 AM6/27/12
to std-pr...@isocpp.org
On 27 June 2012 17:26, Nevin Liber <ne...@eviloverlord.com> wrote:
>> // 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.

Andrzej Krzemieński

unread,
Jun 27, 2012, 10:38:37 AM6/27/12
to std-pr...@isocpp.org


// 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);
 
-- would anyone really want to have these two overloads? Aren't they almost the same thing? Does having the two not indicate some design error?

Andrzej Krzemieński

unread,
Jun 27, 2012, 10:42:18 AM6/27/12
to std-pr...@isocpp.org


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

Really? This makes a huge difference for the design of Optional. I believe It will lose much in useability.

Nevin Liber

unread,
Jun 27, 2012, 10:52:39 AM6/27/12
to std-pr...@isocpp.org
On 27 June 2012 09:38, Andrzej Krzemieński <akrz...@gmail.com> wrote:


// 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);
 
-- would anyone really want to have these two overloads?

Well, the more likely overloads are:

void fun(Expensive const& e)

and

void fun(optional<Expensive> const& e)

Same problem, though.

I don't want to force my users to do an extra copy if they already have the object.  This is especially valid if the original interface didn't have optional.

Aren't they almost the same thing? Does having the two not indicate some design error?

fun might look like:

void fun(optional<Expensive> const& e)
{
    if (e)
        return fun(*e);

    return fun(CreateExpensive());
}
--

Ville Voutilainen

unread,
Jun 27, 2012, 11:03:43 AM6/27/12
to std-pr...@isocpp.org
On 27 June 2012 17:42, Andrzej Krzemieński <akrz...@gmail.com> wrote:
>> +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.

As much as I like being able to say optional<int> = 42; I find it problematic
if f(42); will construct an optional behind my back.

Then again, perhaps that's not a big issue - if the implicit conversion of any
type concerns me, I'll write it as

void f(OptionalWrapper<int>); and do the implicit-conversion-avoidance in
the wrapper.

So, as a counter-argument to my not wanting the implicit conversion, I
do understand
if someone has void f(string), adds void f(optional<string>),
complains, and then
the answer is "don't". :)

Here's a question: is the call f("foo") not ambiguous, if I do that refactoring?

Andrzej Krzemieński

unread,
Jun 27, 2012, 11:13:47 AM6/27/12
to std-pr...@isocpp.org

Would the following  not suit your purpose better?

  void fun(Expensive const& e = CreateExpensive());



Ville Voutilainen

unread,
Jun 27, 2012, 11:16:12 AM6/27/12
to std-pr...@isocpp.org
On 27 June 2012 18:13, Andrzej Krzemieński <akrz...@gmail.com> wrote:
> Would the following  not suit your purpose better?
>   void fun(Expensive const& e = CreateExpensive());

That doesn't look very generic. I may have a function template that can take
an Expensive, or an optional<Expensive>, and call fun with either one. That
signature you wrote would require calling fun() or fun(expensive_object).

Dave Abrahams

unread,
Jun 27, 2012, 10:13:49 PM6/27/12
to std-pr...@isocpp.org

on Wed Jun 27 2012, Nevin Liber <nevin-AT-eviloverlord.com> wrote:

> On 25 June 2012 16:32, Andrzej Krzemieński <akrz...@gmail.com> wrote:
>
>>
>> // 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).

I would too, but this problem should be dealt with by solving the
"first-class std::string literals" issue, not by removing the implicit
conversion to optional.

--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com

Nevin Liber

unread,
Jun 28, 2012, 12:46:20 AM6/28/12
to std-pr...@isocpp.org
While that fixes the specific problem for strings, it doesn't address the general problem with implicit conversions. 

Nevin Liber

unread,
Jun 28, 2012, 12:49:37 AM6/28/12
to std-pr...@isocpp.org
On 27 June 2012 10:13, Andrzej Krzemieński <akrz...@gmail.com> wrote:
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());

There are many ways to solve that particular implementation, but in general I'm not going to refactor my code to use a type that will force an extra copy; rather, I'm just going to add an overload. 
-- 

Fernando Cacciola

unread,
Jun 28, 2012, 1:56:38 PM6/28/12
to std-pr...@isocpp.org
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? (assuming the right implementation of make_optional)

Best

Andrzej Krzemieński

unread,
Jun 28, 2012, 3:45:22 PM6/28/12
to std-pr...@isocpp.org


W dniu czwartek, 28 czerwca 2012 19:56:38 UTC+2 użytkownik Fernando Cacciola napisał:
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?

I guess it might be my way of using and thinking about Optional: that it it the same set of values as that of T, plus one additional value "none":

  optional<unsigned> i = 0;
  optional<unsigned> j = 1;
  optional<unsigned> k = none;

The typical way I use optional is the following:

process( optional<vector<string>> const& arg1 = none ); // possibly more args

void fun()
{
    vector<string> v = something();
    prepare(v);
    process(v);
}


Also, one of the arguments in favor of optional references as a better alternative to raw pointers is that you can initialize the former without the addressof operator:

process( optional<int&> ref = none );

void example()
{
  int i = 0;
  process(i);
}

Without the feature I will have to write:

  process(optional<int&>{i});

In which case, it will be more convenient to just use a pointer.

 

If so, the end user can always use make_optional<string>("hello") if necessary, can't he?

It seems only slightly better than than optional<string>{emplace, "hello"}.
 

Andrzej Krzemieński

unread,
Jun 29, 2012, 6:42:40 AM6/29/12
to std-pr...@isocpp.org
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");

because you know that you are causing an implicit conversion and just asking for problems in case someone (yourself?) adds an overload like:

  void fun(CString s);
  void fun(QString s);

Which are also very likely to provide their own implicit conversions and cause ambiguities. So you probably call function fun() in type-safe manner, like this:

  fun(string("hello"));

Here, typing string("hello") rather than "hello" is the same choice as using type-safe null pointer constant rather than "unsafe" nullptr. If you use fun() like this, and you add the optional overload:

  void fun(string s);
  void fun(optional<string> os);

Then expression:

  fun(string("hello"));

will still pick the correct (unambiguous) overload because copy constructor is preferred to converting constructor. (Am I right about this overload resolution rule?)

I believe, the same applies to overloads:

  void fun(Expensive const& e);

  void fun(optional<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, even w/o optional and just to avoid any potential surprises, you probably call your function like this:

  fun(Expensive(other));

In this case the additional overload with optional will not affect the overload resolution. Right?

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));
  }

This is analogous to providing these three string overloads:


  void fun(string s);
  void fun(optional<string> os);
  void fun(const char * cs) { fun(string(cs)); }

although, I admit that it is not too elegant a solution.

Andrzej Krzemieński

unread,
Jun 29, 2012, 6:52:45 AM6/29/12
to std-pr...@isocpp.org

If this is my concern when doing the refactoring, I can always add a tie-breaking overload:


  void fun(string s);
  void fun(optional<string> os);

  template <class T> requires Convertible<T, string>
  void fun(T&& s) {
    fun( string(std::forward<T>(s)) );
  }

Nevin Liber

unread,
Jun 29, 2012, 2:27:10 PM6/29/12
to std-pr...@isocpp.org
On 29 June 2012 05:42, Andrzej Krzemieński <akrz...@gmail.com> wrote:

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

Just to be clear:  These comments are about a situation where I have a working program that (leaving out references and const for brevity) takes a fun(Foo), and I'm now looking to take a fun(optional<Foo>).  Five reasonable choices:

1.  Don't change the interface; just make the caller handle it.
2.  Change the interface to fun(Foo*).
3.  Add the interface fun(Foo*).
4.  Change the interface to fun(optional<Foo>)
5.  Add the interface fun(optional<Foo>)

1 only works when the callee doesn't have to do something significantly different when there is no object to process.  I actually prefer this option, for the same reason I prefer passing raw references instead of smart pointers to callees that aren't involved with ownership (if the callee doesn't care, I shouldn't burden it, making both the caller and callee easier to reason about); it's worth the slight inconvenience in the caller.

2&3 have the problem with polymorphism vs. value semantics.

4 introduces a copy penalty into an otherwise working program.  Worse, if I need reference semantics, I now have to convert all of the callers to store their Foos in optional<Foo> just to make the interface happy.

That leaves 5.
 

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.

Yes.  People fear the accidental copy problem (performance, slicing, breaking deliberate reference semantics, etc.).  They fear it so much that the (bad) advice I usually hear is to make objects noncopyable so it doesn't happen.
 

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 might (although increasingly I don't), because that is a deliberate and not an accidental copy.  More importantly, I don't want to have to go searching through an entire code base to see if my users have done it (which can be really painful when templates are involved).
 


I believe, the same applies to overloads:

  void fun(Expensive const& e);

  void fun(optional<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,

As I said in a previous post, my default is to not implement things with implicit conversions.  This whole debate has really been about what are the ramifications of implicit conversions.
 


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));
  }

You are going to wait for concepts before proposing any? :-)

While this may change someday, right now enable_if (and I expect the same thing for concepts, at least initially) is an implementation facility for C++ experts.  Experts aren't the only ones writing interfaces.

Fernando Cacciola

unread,
Jun 29, 2012, 2:32:11 PM6/29/12
to std-pr...@isocpp.org
On Thu, Jun 28, 2012 at 4:45 PM, Andrzej Krzemieński <akrz...@gmail.com> wrote:
>
>
> W dniu czwartek, 28 czerwca 2012 19:56:38 UTC+2 użytkownik Fernando Cacciola
> napisał:
>>
>> 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.
>>

Let me clarify my position since upon reading it might not me clear:

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

> The typical way I use optional is the following:
>
> process( optional<vector<string>> const& arg1 = none ); // possibly more
> args
>
> void fun()
> {
>     vector<string> v = something();
>     prepare(v);
>     process(v);

Wich is OK, this is not the case of using an U instead of a T.

> Also, one of the arguments in favor of optional references as a better
> alternative to raw pointers is that you can initialize the former without
> the addressof operator:
>
> process( optional<int&> ref = none );
>
> void example()
> {
>   int i = 0;
>   process(i);

Also OK.

So, what I'm not in favor of is the *converting* constructor being implicit.

In Boost.Optional, construction from T is implicit but construction
from U is not.

--
Fernando Cacciola
SciSoft Consulting, Founder
http://www.scisoft-consulting.com

Nevin Liber

unread,
Jun 29, 2012, 3:14:24 PM6/29/12
to std-pr...@isocpp.org
On 29 June 2012 13:32, Fernando Cacciola <fernando...@gmail.com> wrote:
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

Tentative +1.  I have to think about it a bit, but it feels right so far...

Vladimir Batov

unread,
Jun 29, 2012, 6:36:50 PM6/29/12
to std-pr...@isocpp.org
On Sat, Jun 30, 2012 at 5:14 AM, Nevin Liber <ne...@eviloverlord.com> wrote:
> On 29 June 2012 13:32, Fernando Cacciola <fernando...@gmail.com>
> wrote:
>>
>> 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
>
> Tentative +1.  I have to think about it a bit, but it feels right so far...

I am not a big fan of implicit conversions (far from it). However, the
point (I think) Andrzej is making is that it's not
optional<std::string>'s business to dictate/modify std::string's
behavior. If the std::string's designer decided that an implicit
conversion from "char const*" makes sense for std::string, then
optional<std::string> honors that. I.e.

std::string opt = "hello" ; // if this is OK
optional<string> opt = "hello" ; // then, this is OK also

V.

Vladimir Batov

unread,
Jun 29, 2012, 7:44:48 PM6/29/12
to std-pr...@isocpp.org
On Sat, Jun 30, 2012 at 8:36 AM, Vladimir Batov wrote:
> On Sat, Jun 30, 2012 at 5:14 AM, Nevin Liber wrote:
>> On 29 June 2012 13:32, Fernando Cacciola 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
>>
>> Tentative +1.  I have to think about it a bit, but it feels right so far...
>
> I am not a big fan of implicit conversions (far from it). However, the
> point (I think) Andrzej is making is that it's not
> optional<std::string>'s business to dictate/modify std::string's
> behavior. If the std::string's designer decided that an implicit
> conversion from "char const*" makes sense for std::string, then
> optional<std::string> honors that. I.e.
>
> std::string opt = "hello" ; // if this is OK
> optional<string> opt = "hello" ; // then, this is OK also

My concern though is that allowing 1APF we raise user's expectations
which we cannot (?) fulfill. Consider,

struct Foo
{
Foo(char const*);
Foo(char const*, int);
};

// before refactoring
extern void func(Foo const&);
func("hello"); // Works, calls Foo(char const*)
func({"hello", 1}); // Works, calls Foo(char const*, int)

// after refactoring
extern void func(optional<Foo> const&);
func("hello"); // Works, calls proposed 1APF
func({"hello", 1}); // What's here?

I consider API consistency/predictability quite important. So, if we
allow 1APF, then I'd expect the "What's here?" line to work the
same... which is only possible with PF optional constructors. If so,
then haven't we had an extensive discussion about PF constructors and
had to regretfully reject them in favor of

optional<Foo> o2 = { emplace, "hello", 1 } ;

... or (at least multi-arg) PF constructors are still on the table and
the following is still available

optional<Foo> o2 = { "hello", 1 } ;

Damn, I am confused now. What's the situation with PF constructors?
Are they in? Are they in with some restrictions? Would that be
possible to summarize where we are with regard to PF constructors for
this ol' man sake please?

Ville Voutilainen

unread,
Jun 29, 2012, 7:49:33 PM6/29/12
to std-pr...@isocpp.org
On 30 June 2012 02:44, Vladimir Batov <vb.ma...@gmail.com> wrote:
> Damn, I am confused now. What's the situation with PF constructors?

So are many of us. Here's a moderate suggestion: let's just all shut the
f*** up for a while, we've given plenty of feedback to the proposal authors,
can we please give them a breather to reconsider the various issues
raised, and come back with a new proposal that explains whatever
choices taken? :)

Vladimir Batov

unread,
Jun 30, 2012, 3:50:53 AM6/30/12
to std-pr...@isocpp.org
On Sat, Jun 30, 2012 at 9:49 AM, Ville Voutilainen wrote:
> On 30 June 2012 02:44, Vladimir Batov wrote:
>> Damn, I am confused now. What's the situation with PF constructors?
>
> So are many of us. Here's a moderate suggestion: let's just all shut the
> f*** up for a while, we've given plenty of feedback to the proposal authors,
> can we please give them a breather to reconsider the various issues
> raised, and come back with a new proposal that explains whatever
> choices taken? :)

Even though I would not probably put it as succinctly ;-) I tend to
agree that maybe a pause might be beneficial to collect thoughts and
to put whatever there is into words. Well, I re-read this thread (it's
not that long yet) and the proposal and I feel that a separate
sub-document could be created (and uploaded alongside the orig.
proposal) to serve this thread. In that doc. I'd probably start with
an explicit "Default Construction and Construction of a Disengaged
Optional Instance" section somewhere *early* in the document (to quash
any expectations :-) and to minimize any possible surprises). It'd
state that

1) optional<Guard> o1; // disengaged
2) optional<Guard> o2{}; // same as above
3) optional<Guard> o3{none}; // same as above
4) optional<Guard> o4 = {}; // same as above
5) optional<Guard> o5 = none; // same as above
6) optional<Guard> o6 = {none}; // same as above

It'd state also that other value-initialization behavior have been
carefully considered and ultimately rejected as such behavior would be
decidedly different from (and, subsequently, confusing compared to)
the current practice established by std::vector, std::shared_ptr, etc.

Then, the next section "Direct-Initialization and Copy-Initialization
of an Engaged Optional Instance" with

optional<Guard> o4{emplace, 2, "2"}; // calls Guard{2, "2"} in-place
optional<Guard> o5{emplace}; // calls Guard{} in-place.
and
optional<Guard> o4 = {emplace, 2, "2"}; // calls Guard{2, "2"} in-place
optional<Guard> o5 = {emplace}; // calls Guard{} in-place.

I'd probably state that it is not the only available construction
interface (if it's decided to have more). However, it is the
preferred, most explicit and unambiguous construction mechanism.

Then, I'd probably expect a brief separate "Copy and Move
Constructors" section for completeness sake.

So, that way I think we might have the first 3 sections which do not
generate much debate and which we (I hope) agree are a solid and
workable foundation.

Now the fun begins. :-) A purist might say that the described above
API is all that is necessary (i.e. minimal but complete). However, we
live in a real world so sometimes we appreciate a trick or two to chug
along without much struggle. One such thing to consider would be
Andrzej's suggestion:

> We could add a converting constructor:
> template <class U> requires Convertible<U, T>
> optional<T>::optional(U&& v);
>
> 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

My initial reaction was 'nice'. Then, I thought if

// after refactoring
void fun(optional<Foo>);
fun("hello"); // still compiles
fun({"hello"}); // How about this?
fun({"hello", 1}); // How about this?

So, (as I understand) if we let 1APF, then full PF knocks on the door
with "what about me?". If my memory serves me, deploying PF failed on
the strategic (so to speak) level. So, we stated right off-the-bat the
first three sections what optional does. Now, if we are considering PF
(with no-emplace-tag as in Section 2) again, then it's only on the
tactical (so to speak), convenience level to help us with, say,
refactoring. In other words, we'll take it as far as it does not clash
with the first 3 sections. Here I personally in-between two chairs.

Have I gotten it all right so far? Missed, mus-interpreted,
mus-represented something? Simply made a fool of myself? Whatever that
might be, that won't be for the first time. So, no drama. :-)

Chris Jefferson

unread,
Jun 30, 2012, 6:41:02 AM6/30/12
to std-pr...@isocpp.org
On 30/06/12 08:50, Vladimir Batov wrote:
>
>
> It'd state also that other value-initialization behavior have been
> carefully considered and ultimately rejected as such behavior would be
> decidedly different from (and, subsequently, confusing compared to)
> the current practice established by std::vector, std::shared_ptr, etc.
>
> Then, the next section "Direct-Initialization and Copy-Initialization
> of an Engaged Optional Instance" with
>
> optional<Guard> o4{emplace, 2, "2"}; // calls Guard{2, "2"} in-place
> optional<Guard> o5{emplace}; // calls Guard{} in-place.
> and
> optional<Guard> o4 = {emplace, 2, "2"}; // calls Guard{2, "2"} in-place
> optional<Guard> o5 = {emplace}; // calls Guard{} in-place.
>
> I'd probably state that it is not the only available construction
> interface (if it's decided to have more). However, it is the
> preferred, most explicit and unambiguous construction mechanism.

I feel I have to disagree with this :)

I've used both boost::optional, and my own optional, in many code bases
for a long time. While I understand why people want an 'emplace' style
interface, I have looked back through my code and never found a place
where I would have wanted to use it.

I have always used optionals in one of two ways:

1) Construct an empty optional
2) Construct an optional<T> from a T.

My view (possibly different to others) was that the 'emplace' interface
was there for cases where high efficiency is required, or for types
without a copy constructor.

While I realise we have to deal with special cases, I was hoping that
the emplace interface would be an 'advanced user' feature, not the
standard method of use.

Chris

Dave Abrahams

unread,
Jun 30, 2012, 7:11:07 AM6/30/12
to std-pr...@isocpp.org

on Sat Jun 30 2012, Chris Jefferson <chris-AT-bubblescope.net> wrote:

> While I realise we have to deal with special cases, I was hoping that
> the emplace interface would be an 'advanced user' feature, not the
> standard method of use.

+1

Vladimir Batov

unread,
Jun 30, 2012, 8:13:10 AM6/30/12
to std-pr...@isocpp.org
On Sat, Jun 30, 2012 at 8:41 PM, Chris Jefferson wrote:
> On 30/06/12 08:50, Vladimir Batov wrote:
>>
>> It'd state also that other value-initialization behavior have been
>> carefully considered and ultimately rejected as such behavior would be
>> decidedly different from (and, subsequently, confusing compared to)
>> the current practice established by std::vector, std::shared_ptr, etc.
>>
>> Then, the next section "Direct-Initialization and Copy-Initialization
>> of an Engaged Optional Instance" with
>>
>> optional<Guard> o4{emplace, 2, "2"}; // calls Guard{2, "2"} in-place
>> optional<Guard> o5{emplace}; // calls Guard{} in-place.
>> and
>> optional<Guard> o4 = {emplace, 2, "2"}; // calls Guard{2, "2"} in-place
>> optional<Guard> o5 = {emplace}; // calls Guard{} in-place.
>>
>> I'd probably state that it is not the only available construction
>> interface (if it's decided to have more). However, it is the
>> preferred, most explicit and unambiguous construction mechanism.
>
> I feel I have to disagree with this :)

Could you please elaborate what exactly you feel you have to disagree
with? IMO it's hard to argue that it's *not* sufficiently explicit.
Equally, I find it unambiguous (unlike others considered) and that's
the reason we've settled on it. And seemingly unconditionally the most
efficient. So, to *me* that makes it preferred from the
long-term/maintenance POV.

> I've used both boost::optional, and my own optional, in many code bases for
> a long time. While I understand why people want an 'emplace' style
> interface, I have looked back through my code and never found a place where
> I would have wanted to use it.

Hmm, I feel it's not like we *wanted* 'emplace'. I for one fought hard
(and lost... I think) for the args perfectly-forwarded to the
underlying object. I think we settled on 'emplace' because we had to,
to make sure a general-purpose library provides unambiguous consistent
API. Something I personally value highly.

> I have always used optionals in one of two ways:
>
> 1) Construct an empty optional
> 2) Construct an optional<T> from a T.

Understood. The problem with #1 is that it's still here (a good thing)
but it has a new {}-based syntax in addition to the old syntax. So, if
I listed all, we have 6 (damn!) ways of creating a disengaged
optional. The special treatment of {} initialization leads to possible
confusion:

1) optional<Foo> foo = { "hello", 1 }; // your #2 choice
2) optional<Foo> foo = {}; // your #1 choice

Potential confusion as #1 creates engaged optional by constructing an
optional<Foo> from Foo::Foo(char const*, int) (for simplicity sake)
but #2 does *not* construct an optional<Foo> from Foo::Foo().

The problem with #2, as I see it, is that incurs inherent
double-construction overhead in "optional<Foo> foo = Foo();" Indeed
that overhead might be optimized away for movable classes. However,
I'd argue that "movability" usually involves a pointer somewhere deep
inside... and with that pointer often comes that "optionality"
functionality (via pointer=0). Therefore, optional is more relevant
for non-movable classes. Having said that, I understand that the
double-construction of optional<int> is negligible. But again, I can
understand the reasons why Andrzej stated

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

It was not an issue with C++03. I am sure any suggestions would be
most welcomed. Maybe, indeed, we need to have another look at
perfect-forwarding (with no 'emplace') and summarize where it cannot
be used.

> My view (possibly different to others) was that the 'emplace' interface was
> there for cases where high efficiency is required, or for types without a
> copy constructor.
> While I realise we have to deal with special cases, I was hoping that the
> emplace interface would be an 'advanced user' feature, not the standard
> method of use.

I understand your reasoning. I really do. My view though is that for a
general-purpose library (especially of such a wide scope as std)
consistency of the interface is very high on the list. It is achieved
with 'emplace' and I feel it should be mentioned in the documentation
first as it always works. So, if one decides to never read beyond that
chapter/section, he'll be able to use optional<>. That is, in my view,
*is* the basic usage/deployment rather than advanced.

Lastly, my prev. email was merely an attempt to organize our effort by
documenting something that we seemingly agree on to avoid re-hashing
those same issues and to start small... and then build on that. In
other words, those 3 sections that I suggested (please note
'suggested' Andrzej is the driver, I am just a noisy back-seat
passenger) are merely the beginning. I'd expect next sections to deal
with specific ("advanced") :-) cases where copy overhead is not an
issue, etc. etc... as long as they do not negate the previous
sections... Or Andrzej might conclude that this ol' man's long lost
his marbles. So, be alert but do not be alarmed. :-)

Vladimir Batov

unread,
Jun 30, 2012, 9:42:28 AM6/30/12
to std-pr...@isocpp.org
On Sat, Jun 30, 2012 at 9:11 PM, Dave Abrahams wrote:
> on Sat Jun 30 2012, Chris Jefferson wrote:
>
>> While I realise we have to deal with special cases, I was hoping that
>> the emplace interface would be an 'advanced user' feature, not the
>> standard method of use.
>
> +1

Yes, the more I think about it the more I feel that the following
natural interface simply cannot be discarded/ignored.

std::string str = "hello";
Foo foo = { "hello", 1 };

optional<std::string> str = "hello";
optional<Foo> foo = { "hello", 1 };

Giving something uniform, unambiguous, working 100% but cumbersome and
leaving 80% dissatisfied does not seem like a sensible approach. So,
the above can be done with

1) optional<T>(T const&) (additional construction overhead)
2) PF (no additional overhead)

Could we list all confusing/breaking cases and then consider how
realistic/important they are and, say, to try and ban them, i.e. force
the 'emplace' API?

Chris Jefferson

unread,
Jun 30, 2012, 10:44:16 AM6/30/12
to std-pr...@isocpp.org
I think I misunderstand something, and might be testing on compilers
which don't yet work properly. I would be grateful if you could help me
with my error.

The interface I would like to see is the one suggested in the original
draft, so the interesting constructors are:

template<typename T>
struct optional
{
optional() = default;
optional(const optional&) = default;
optional(optional&&) = default;
optional(T&&);
optional(const T&)
template<class... Args>
optional(emplace_t, Args&&...);
};

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.


Vladimir Batov

unread,
Jun 30, 2012, 11:05:46 PM6/30/12
to std-pr...@isocpp.org
On Sat, Jun 30, 2012 at 5:50 PM, Vladimir Batov <vb.ma...@gmail.com> wrote:
> ... I'd probably start with
> an explicit "Default Construction and Construction of a Disengaged
> Optional Instance" section somewhere *early* in the document (to quash
> any expectations :-) and to minimize any possible surprises). It'd
> state that
>
> 1) optional<Guard> o1;  // disengaged
> 2) optional<Guard> o2{}; // same as above
> 3) optional<Guard> o3{none}; // same as above
> 4) optional<Guard> o4 = {}; // same as above
> 5) optional<Guard> o5 = none; // same as above
> 6) optional<Guard> o6 = {none}; // same as above

With

template<class T> struct optional
{ ...
explicit optional() {}
};

We can eliminate

optional<Foo> opt2 = {}; // Not allowed. Good IMO.

Then, I played with 'none'.

template<class T> struct optional
{ ...
optional(none_t) {}
};

void func(optional<Foo> const&);

That 'none' has such a limited usability/usefulness that I am not sure
how we've managed to seriously discuss it for so long. As soon as I
add a func() overload, 'none' chokes on it. So much for a generic
facility. :-(

Then, I tried the tried and trusty copy cnstr instead:

template<class T> struct optional
{ ...
static optional const null;
explicit optional(optional const&) {}
};
template<class T> optional<T> const optional<T>::null;
void func(optional<Foo> const&);

That seemed quite adequate:

4) optional<Foo> opt4{optional<Foo>::null};
5) func(optional<Foo>::null);

#4 creates a disengaged optional (for those who favor explicitness)
and #5 seems to work reliably and unambiguously. So, I'd suggest

template<class T> struct optional
{ ...
static optional const null;
explicit optional() {}
explicit optional(optional const&) {}
};

That has the advantages of managing without *any* none-related types
and cutting down the number of "disengaged" constructors:

> 1) optional<Guard> o1; // disengaged
> 2) optional<Guard> o2{}; // same as above
> 3) optional<Guard> o6{optional<Guard>::null}; // same as above

Vladimir Batov

unread,
Jun 30, 2012, 11:57:59 PM6/30/12
to std-pr...@isocpp.org
On Sun, Jul 1, 2012 at 12:44 AM, Chris Jefferson wrote:
> ...
> I think I misunderstand something, and might be testing on compilers which
> don't yet work properly. I would be grateful if you could help me with my
> error.
>
> The interface I would like to see is the one suggested in the original
> draft, so the interesting constructors are:
>
> template<typename T>
> struct optional
> {
>    optional() = default;
>    optional(const optional&) = default;
>    optional(optional&&) = default;
>    optional(T&&);
>    optional(const T&)
>    template<class... Args>
>    optional(emplace_t, Args&&...);
> };
>
> 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.

Indeed. For the "optional<Record> john = {"John, 5500}" to work we need either

1) with the proposed 'emplace' to change to:
optional<Record> john = {emplace, "John, 5500}"
2) extend the API:

struct optional
{
template<class... Args> optional(Args&&...);
};

3) or optional<Record> john = Record{"John, 5500}

Andrzej Krzemieński

unread,
Jul 2, 2012, 4:31:39 AM7/2/12
to std-pr...@isocpp.org

The following is my understanding of what we arrived at:

PF (or "Variadic forwarding constructor") is rejected, mostly due to the case or zero-argument constructor:

vector<int> v{}; // zero element vector
optional<vector<int>> ov{}; // disengaged optional.

Note that you cannot eliminate this problem with explicit default constructor. The second counter-argument is the typical current and expected use of optional:

optional<char> readNextCar()
{
  optional<char> ans;

  if (hasNextChar()) {
    ans = popNextChar();
  }

  return ans;
}

There were other (IMO less important) concerns of irregularity in case of constructor from tag 'none' and constructor T(optional<T>). variadic FP is (at least for now) out. We are left with what you have described:

1) default ctor -- creates a disengaged optional object
2) implicit conversion from tag 'none' -- this is not strictly necessary (and controversial)
3) explicit "emplace" constructor.
4) copy/move - of course (but I do not think you can/should make them explicit)

Now I am just prompting the interest in other construction variants:
A) The "converting constructor" (or 1APF) without a variadic PF -- but it also looks we are not going to have it.

I also intend to check the following:
B) "implicit" constructor from T:
   optional<T>::optional(T const&);
   optional<T>::optional(T &&);

C) explicit constructor from T

D) "restricted" constructor from T:
   template <typename T2>
   requires SameType<RemoveRef<T2>, T>
   optional( T2 && v);

The last one (D) is very similar to (B) except that it disables some forms of deduction in overload resolution, like:
  optional<Record> or = {"John", "Smith"}; // error
  optional<Record> or = Record{"John", "Smith"}; // ok

I definitely need to summarize all the current discussion. However, I wanted to still wait for feedback on other parts of the interface. I expected that some other items would rise more discussion or objections, like optional references (their existence), or constexpr default constructor. Note that providing constexpr guarantee for optional's default constructor is very difficult and very restrictive on implementations. Te reference implementation uses a trick with union, but other implementers may not find it acceptable.

Andrzej Krzemieński

unread,
Jul 2, 2012, 4:45:10 AM7/2/12
to std-pr...@isocpp.org


I believe that (if PF solution is rejected), your above list (with perhaps some minor tweaks) is what everyone agrees with. Am I right? 


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 cannot verify it right now, but I believe that when you type:

  optional<Record> john = {"John, 5500};

Compiler treats it as: 
 
  optional<Record> john = X{"John, 5500};

And tries to deduce X. If the deduction is unambiguous the example works fine, and given the above constructor set, the best match (and unambiguous is X == T.



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.

I guess this is viable comprehensive solution. Note that there is another comprehensive set of constructors possible:
1. PF constructor
2. Special constuctor indicating disengaged state.

At this point it has been rejected due to the expected confusion that it might cause.

Vladimir Batov

unread,
Jul 2, 2012, 7:59:20 AM7/2/12
to std-pr...@isocpp.org
On Mon, Jul 2, 2012 at 6:45 PM, Andrzej Krzemieński wrote:
> ...
>> 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 cannot verify it right now, but I believe that when you type:
>
> optional<Record> john = {"John, 5500};
>
> Compiler treats it as:
>
> optional<Record> john = X{"John, 5500};

My reading of the Standard is that for "optional<Record> john =
{"John, 5500};" to work optional needs to have PF-based
optional(Args...) constructor. The compiler does not try to implicitly
convert {...} to Record and then apply optional*Record const&). To
make sure I tried it with gcc-4.6. It works though with with minor
modifications:

optional<Record> john = Record{"John, 5500};

Vladimir Batov

unread,
Jul 2, 2012, 8:11:04 AM7/2/12
to std-pr...@isocpp.org
or optional<Record> john = {emplace, "John, 5500};

Richard Smith

unread,
Jul 2, 2012, 4:32:40 PM7/2/12
to std-pr...@isocpp.org
This set of constructors is not comprehensive. There would be no correct way to write a function which takes (T &&...args) and constructs an optional<U> with them -- if given a single argument of type std::none_t (or whatever type indicates that we use the special 'disengaged' constructor), that argument will not be forwarded to U's constructor. This means perfect forwarding fails for (at least) the case of optional<optional<T>>.

Vladimir Batov

unread,
Jul 2, 2012, 4:35:51 PM7/2/12
to std-pr...@isocpp.org
On Mon, Jul 2, 2012 at 6:31 PM, Andrzej Krzemieński <akrz...@gmail.com> wrote:
> The following is my understanding of what we arrived at:
> ...
> I also intend to check the following:
> B) "implicit" constructor from T:
> optional<T>::optional(T const&);
> optional<T>::optional(T &&);
>
> C) explicit constructor from T
>
> D) "restricted" constructor from T:
> template <typename T2>
> requires SameType<RemoveRef<T2>, T>
> optional( T2 && v);
>
> The last one (D) is very similar to (B) except that it disables some forms
> of deduction in overload resolution, like:
> optional<Record> or = {"John", "Smith"}; // error
> optional<Record> or = Record{"John", "Smith"}; // ok

I played with the non-templated constructors listed in the proposal
and *all* seem sensible and, in fact, quite essential (well, with the
special case of opt(none_t)). More so, they look good to me as they
are. In one of my emails I mentioned that I toyed with the idea of
applying 'explicit' but that turned out to be over-zealous and silly.
With those mentioned non-templated constructors I got the following
(reasonable IMO) deployment examples:

int k = 5;
Foo foo;

// Creating a disengaged optional
optional<Foo> opt00; // Calls opt()
optional<Foo> opt01{}; // Same as above
optional<Foo> opt04 = {}; // Not allowed if 'explicit opt()'
optional<Foo> opt03{optional<Foo>::null}; // Calls opt(opt const&)
optional<Foo> opt05 = {optional<Foo>::null}; // same as above
optional<Foo> opt06 = optional<Foo>::null; // same as above

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

V.

Vladimir Batov

unread,
Jul 2, 2012, 4:50:53 PM7/2/12
to std-pr...@isocpp.org
On Tue, Jul 3, 2012 at 6:32 AM, Richard Smith wrote:
> On Mon, Jul 2, 2012 at 1:45 AM, Andrzej Krzemieński wrote:
>>> ...
>>> * 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.
>>
>> I guess this is viable comprehensive solution. Note that there is another
>> comprehensive set of constructors possible:
>> 1. PF constructor
>> 2. Special constuctor indicating disengaged state.
>
> This set of constructors is not comprehensive. There would be no correct way
> to write a function which takes (T &&...args) and constructs an optional<U>
> with them -- if given a single argument of type std::none_t (or whatever
> type indicates that we use the special 'disengaged' constructor), that
> argument will not be forwarded to U's constructor. This means perfect
> forwarding fails for (at least) the case of optional<optional<T>>.

1) template <class ...Args> optional (Args&&... args)
2) template <class ...Args> optional (emplace_t, Args&&... args)

Yes, it seems that #1 would be highly desirable for some deployments
but might not be able to live up to the task on the generic-library
level. So, #2 is being proposed instead and hopes for #1 are fading
fast.

Still, this optional<optional<T>> has been mentioned before and I
can't resist mentioning that at the first (and second) glance it looks
silly. My OTOH inclination/suggestion would be to disable this case
as no matter how hard I try I cannot think why one'd like to do that.
I do understand though that for generic programming such a situation
might occur. Then, I'd expect it to be resolved with SFINAE to avoid
optional<optional<>>.







This argument

Nevin Liber

unread,
Jul 2, 2012, 4:59:55 PM7/2/12
to std-pr...@isocpp.org
On 2 July 2012 15:50, Vladimir Batov <vb.ma...@gmail.com> wrote:

> Still, this optional<optional<T>> has been mentioned before and I
> can't resist mentioning that at the first (and second) glance it looks
> silly. My OTOH inclination/suggestion would be to disable this case
> as no matter how hard I try I cannot think why one'd like to do that.

Every case which is disabled breaks uniformity and makes generic
programming that much harder.

I'm not saying that we can't break uniformity; rather, I'm hoping the
bar is a bit higher than we can't think of a use case for it at the
moment. (That is one of my arguments in favor of optional references
as well).

Vladimir Batov

unread,
Jul 2, 2012, 5:07:56 PM7/2/12
to std-pr...@isocpp.org
On Tue, Jul 3, 2012 at 6:59 AM, Nevin Liber wrote:
> On 2 July 2012 15:50, Vladimir Batov wrote:
>
>> Still, this optional<optional<T>> has been mentioned before and I
>> can't resist mentioning that at the first (and second) glance it looks
>> silly. My OTOH inclination/suggestion would be to disable this case
>> as no matter how hard I try I cannot think why one'd like to do that.
>
> Every case which is disabled breaks uniformity and makes generic
> programming that much harder.
>
> I'm not saying that we can't break uniformity; rather, I'm hoping the
> bar is a bit higher than we can't think of a use case for it at the
> moment. (That is one of my arguments in favor of optional references
> as well).

If you argue for uniformity, then you'll be hard press to find a
better supported than me. :-) Having said that, as they say, any
sensible thing pushed to its limits becomes absurd. Say, std::list
might provide op[] for uniformity sake. However, it does not as doing
so is most certainly (?) a design/implementation/deployment error. I
feel that same logic is applicable with regard to
optional<optional<>>.

Vladimir Batov

unread,
Jul 2, 2012, 5:09:25 PM7/2/12
to std-pr...@isocpp.org
Jeez, need to slow down. Meant to say

If you argue for uniformity, then you'll be hard pressED to find a
better supporteR than me. :-)

Nevin Liber

unread,
Jul 2, 2012, 5:30:51 PM7/2/12
to std-pr...@isocpp.org
On 2 July 2012 16:07, Vladimir Batov <vb.ma...@gmail.com> wrote:

> If you argue for uniformity, then you'll be hard press to find a
> better supported than me. :-) Having said that, as they say, any
> sensible thing pushed to its limits becomes absurd. Say, std::list
> might provide op[] for uniformity sake. However, it does not as doing
> so is most certainly (?) a design/implementation/deployment error.

There are most certainly use cases for it (I've certainly had
algorithms where I've had to look ahead or look behind); it's just
that you don't want people accidentally invoking an O(n) operation.
Like I said, I'm hoping for a higher bar than "can't think of how to
use it right now."

I can contrive a situation of optional<optional<T>>. Suppose I use
optional<T> to model a value being read from a database (this part is
not contrived). Now I want to model whether or not the database
existed that contained the value, so I use optional<optional<T>>.

Unless it is breaking something else, I don't see the reason to ban it.

Ville Voutilainen

unread,
Jul 2, 2012, 5:34:01 PM7/2/12
to std-pr...@isocpp.org
On 3 July 2012 00:30, Nevin Liber <ne...@eviloverlord.com> wrote:
> I can contrive a situation of optional<optional<T>>.  Suppose I use
> optional<T> to model a value being read from a database (this part is
> not contrived).  Now I want to model whether or not the database
> existed that contained the value, so I use optional<optional<T>>.
> Unless it is breaking something else, I don't see the reason to ban it.

Same here, I don't see a reason to ban it either. If it causes
perfect-forwarding
problems, are those problems any different from ones that arise when having
a tuple of a tuple, and using the allocator_arg_t constructor?

Richard Smith

unread,
Jul 2, 2012, 5:40:04 PM7/2/12
to std-pr...@isocpp.org
I think you're not being imaginative enough.

// Library 1
typedef optional<int> limit; // disengaged means "no limit"

// Library 2
struct no_override_t {} no_override;
struct override_t {} override_to;
template<typename T>
struct override {
  optional<T> value; // disengaged means "no override"

  override(no_override_t) : value{ std::none } {}

  template<typename...U>
  override(override_t, U &&...u) : value{std::forward<U>(u)...} {}

  void apply(T &t) { if (value) t = *value; }
};

// Client
override<limit> overrideToNoLimit = { override_to, std::none }; // oops!
limit lim = 5;
overrideToNoLimit.apply(lim);

Should we really require the implementor of 'override' to reimplement 'optional' in order to produce a working set of constructors?

Vladimir Batov

unread,
Jul 2, 2012, 5:59:10 PM7/2/12
to std-pr...@isocpp.org
On Tue, Jul 3, 2012 at 7:40 AM, Richard Smith wrote:
> On Mon, Jul 2, 2012 at 2:07 PM, Vladimir Batov wrote:
>> ...
>> If you argue for uniformity, then you'll be hard press to find a
>> better supported than me. :-) Having said that, as they say, any
>> sensible thing pushed to its limits becomes absurd. Say, std::list
>> might provide op[] for uniformity sake. However, it does not as doing
>> so is most certainly (?) a design/implementation/deployment error. I
>> feel that same logic is applicable with regard to
>> optional<optional<>>.
>
> I think you're not being imaginative enough.

After such a foreword I thought OK, smarty pants, let's see your
example. :-) However, after looking at it I found it very convincing,
cleanly and sensibly done. So, I stand corrected. Thank you.

Chris Jefferson

unread,
Jul 2, 2012, 6:03:50 PM7/2/12
to std-pr...@isocpp.org
After looking through my code, I've found I'm already using
optional<optional<>>

I have functions which return optional<int> to add a 'NaN' value to int.

I have a memorizer for a function X -> Y which stores optional<X> to
denote values which we have not yet been memorized.

Together these lead to having optional<optional<int> >.

Chris

Vladimir Batov

unread,
Jul 2, 2012, 6:07:53 PM7/2/12
to std-pr...@isocpp.org
On Tue, Jul 3, 2012 at 8:03 AM, Chris Jefferson wrote:
> On 02/07/12 22:07, Vladimir Batov wrote:
> ...
>> If you argue for uniformity, then you'll be hard press to find a
>> better supported than me. :-) Having said that, as they say, any
>> sensible thing pushed to its limits becomes absurd. Say, std::list
>> might provide op[] for uniformity sake. However, it does not as doing
>> so is most certainly (?) a design/implementation/deployment error. I
>> feel that same logic is applicable with regard to
>> optional<optional<>>.
>
> After looking through my code, I've found I'm already using
> optional<optional<>>
>
> I have functions which return optional<int> to add a 'NaN' value to int.
>
> I have a memorizer for a function X -> Y which stores optional<X> to denote
> values which we have not yet been memorized.
>
> Together these lead to having optional<optional<int> >.

Indeed, that sounds quite sensible as well. So, here, I admit it. I've
been soundly beaten. I'll crawl into my dark corner and leak the
wounds.

Vladimir Batov

unread,
Jul 2, 2012, 6:09:16 PM7/2/12
to std-pr...@isocpp.org
On Tue, Jul 3, 2012 at 8:07 AM, Vladimir Batov <vb.ma...@gmail.com> wrote:
> ...
> Indeed, that sounds quite sensible as well. So, here, I admit it. I've
> been soundly beaten. I'll crawl into my dark corner and leak the
> wounds.

Not again! s/leak/lick

Andrzej Krzemieński

unread,
Jul 3, 2012, 2:43:06 AM7/3/12
to std-pr...@isocpp.org

I remember form your other post that "optional<Foo>::null" of type optional<Foo>. Am I right? If so, using these form of initialization requires optional<Foo> to be MoveConstructible and therefore it requires Foo to be MoveConstructible. Therefore such initialization will not work for Guard-like types:

optional<Guard> g1 = optional<Guard>::null; // ERROR
optional<Guard> g2{ optional<Guard>::null } // ERROR

The constructor that would use a simple tag would work for the second line (g2), because it requires no move constructor.
If you feel strong about type-safe 'null' it is still achievable with some parametrized tag.


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

The "templated constructors" are the result of my misunderstanding of how list initialization works.That is, I incorrectly thought that this

  optional<SuperType> o = {arg1, arg2};

would also consider all constructors of SuperType. Since this is not the case, you can treat the "templated constructors" as my mistake and ignore them.

 

Andrzej Krzemieński

unread,
Jul 3, 2012, 2:58:27 AM7/3/12
to std-pr...@isocpp.org
This problem is fixed by using a parametrized "null" (or "none") tag as Vladimir suggests. When you explicitly state the type of "null", it is clear to which optional it applies:

optional<optional<int>> ooi = optional<int>::null; // engaged
optional<optional<int>> ooi = optional<optional<int>>::null; // disengaged

But in general, you are correct that perfect forwarding will fail at some point anyway. Even with this constructor:
template <class ...Args> explicit optional (emplace_t, Args&&... args);

It is not possible to perfect-forward initializer_list argument, because the rules for template type deduction make the special case for it. This is why the current proposal offers one more PF constructor:

  template <class U, class... Args>
  explicit optional(emplace_t, initializer_list<U>, Args&&...);

It is more of a hack. It works under the assumption that if your constructors have initializer_list argument, it will come first in the argument list. This assumption works for STL types, but if you have a constructor that takes two initialize lists, it will still not work.

To me, "perfect forwarding" only means "involving variadic templates, and reference collapsing" rather than "perfection".



Vladimir Batov

unread,
Jul 3, 2012, 5:33:03 PM7/3/12
to std-pr...@isocpp.org
On Tue, Jul 3, 2012 at 4:43 PM, Andrzej Krzemieński wrote:
> ...
>> optional<Foo> opt03{optional<Foo>::null}; // Calls opt(opt const&)
>> optional<Foo> opt05 = {optional<Foo>::null}; // same as above
>> optional<Foo> opt06 = optional<Foo>::null; // same as above
>
> I remember form your other post that "optional<Foo>::null" of type
> optional<Foo>. Am I right? If so, using these form of initialization
> requires optional<Foo> to be MoveConstructible and therefore it requires Foo
> to be MoveConstructible. Therefore such initialization will not work for
> Guard-like types:
>
> optional<Guard> g1 = optional<Guard>::null; // ERROR
> optional<Guard> g2{ optional<Guard>::null } // ERROR

Indeed. And you did explain it to me before. My bad. Sorry. So, after
all the efforts :-) we are back to the original

struct optional
{ ...
optional(std::none_t);
}

or the "built-into-optional" one

struct optional
{ ...
struct null_t {}; // Or none, or nothing, etc.
static null_t null;
optional(this_type::null_t);
}

optional<Guard> g1 = optional<Guard>::null;
optional<Guard> g2{ optional<Guard>::null }

Now these two lines above work, right?.. And no memory wasted for
static optional<T> optional<T>::null_or_none; Makes sense.

> ...
> The "templated constructors" are the result of my misunderstanding of how
> list initialization works...
> ... you can treat the "templated constructors" as my mistake and ignore
> them.

Wonderful. Fewer the merrier.

Richard Smith

unread,
Jul 3, 2012, 9:53:40 PM7/3/12
to std-pr...@isocpp.org
I think the problem is unavoidable. If you want to forward all possible combinations of argument types, you cannot reserve a function signature to mean something else. Here's a case where even the typed 'none' value doesn't help:

  struct value {
    value(optional<value> prototype);
    // ...
  };

  template<typename T>
  struct override {
    optional<T> value; // disengaged means "no override"

    override(no_override_t) : value{ optional<T>::none } {}

    template<typename...U>
    override(override_t, U &&...u) : value{ std::forward<U>(u)... } {}
  };

  override<value> o { override_as, optional<value>::none }; // oops

Vladimir Batov

unread,
Jul 3, 2012, 11:20:36 PM7/3/12
to std-pr...@isocpp.org
On Wed, Jul 4, 2012 at 11:53 AM, Richard Smith wrote:
> On Mon, Jul 2, 2012 at 11:58 PM, Andrzej Krzemieński wrote:
>>
>> This problem is fixed by using a parametrized "null" (or "none") tag as
>> Vladimir suggests. When you explicitly state the type of "null", it is clear
>> to which optional it applies:
>>
>> optional<optional<int>> ooi = optional<int>::null; // engaged
>> optional<optional<int>> ooi = optional<optional<int>>::null; // disengaged
>
> I think the problem is unavoidable. If you want to forward all possible
> combinations of argument types, you cannot reserve a function signature to
> mean something else. Here's a case where even the typed 'none' value doesn't
> help:
>
> struct value {
> value(optional<value> prototype);
> // ...
> };
>
> template<typename T>
> struct override {
> optional<T> value; // disengaged means "no override"
>
> override(no_override_t) : value{ optional<T>::none } {}
>
> template<typename...U>
> override(override_t, U &&...u) : value{ std::forward<U>(u)... } {}
> };
>
> override<value> o { override_as, optional<value>::none }; // oops

Hmm, I am not sure. I might have misunderstood the code. However, it
seems in the example above it needs to be clarified what we actually
want to override with this:

override<value> o { override_as, optional<value>::none }; // oops

If that is override<value>::value (the variable inside override), then
it works as expected (I think). If the code is trying to call
value::value(optional<value>), then the code needs to be called as

override<value> o { override_as, emplace, optional<value>::none };

or alternatively the code needs to be modified as

template<typename...U>
override(override_t, U &&...u) : value{emplace, std::forward<U>(u)... } {}

Andrzej Krzemieński

unread,
Jul 4, 2012, 5:25:53 AM7/4/12
to std-pr...@isocpp.org

Again, I am saying this without having checked that with the compiler: The second one does work. The first one, I believe, is a copy-initialization and requires optional<Guard> to be MoveConstructible.

Andrzej Krzemieński

unread,
Jul 11, 2012, 5:44:47 AM7/11/12
to std-pr...@isocpp.org
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

Tentative +1.  I have to think about it a bit, but it feels right so far...

I faced an interesting problem yesterday caused by boost::optional's implicit conversion from T. To simplify it a little: I have a (non-static) class member passengerCount of type unsigned:

class MyPlan
{
  unsigned passengerCount;
  // other members
};

Now I realized that in some situations I cannot compute the count and I need to have my object "partially created", in the sense that passengerCount is marked as "non computed". I use boost::optional for that purpose:

class MyPlan
{
  optional<unsigned> passengerCount;
  // other members
};

And I expected (judge yourself if this expectation was valid) that compiler would warn me now about every attempt to read the value of passengerCount because of the change in the interface. Optional has purposely an asymmetric interface: assigning to optional<T> looks the same as assigning to T because this operation is always safe. But reading requires additional syntax, because we need to take care of the "disengaged" (or empty) state. In general, I cannot expect that shifting from T to optional<T> will make the compiler warn me about every read, because T itself may expose interface similar to optional's (e.g. T may be a pointer), but my type is unsigned int.

however, I found that the function exceedsCapacity still compiled after the change:

bool  MyPlan::exceedsCapacity() const
{
  return passengerCount > availableSeats;
  // availableSeats is of type unsigned int
}

At first glance, I would think that this code would be rejected, but due to implicit converting constructor compiler picked optional's comparison, and unfortunately (to me) the logic of comparison is that disengaged state is treated as the least possible value (rather than not-a-value) and in case I failed to compute passengerCount function exceedsCapacity() incorrectly stated that the capacity has not been exceeded, counter to my expectation: I thought it would not compile.

I am not entirely sure if this is a problem of implicit conversions per se, or perhaps the fact that relational operators come as part of library's interface and are not prepared for this situation (comparisons between T and optional<T> could be poisoned and I cannot imagine any other situation whatsoever where an analogous problem would appear).
Reply all
Reply to author
Forward
0 new messages