8

I have read on Stack Overflow in many posts that when pointer is used (for argument or return value) and nullptr is allowed (making it optional), it is generally better to use std::optional instead.

However what if the pointer refers to polymorphic type? Is it best to use std::optional or a pointer?

Tas
  • 7,023
  • 3
  • 36
  • 51
John Lock
  • 623
  • 1
  • 7
  • 18
  • 1
    "*that when pointer is used and nullptr is allowed, it is generally better to use std::optional instead.*" Really, that's not what it's for. And trying to do that universality can have many different effects. – Nicol Bolas May 22 '16 at 00:49
  • 1
    Where have you read this? It's rubbish. – James May 22 '16 at 01:32
  • @James For example here (http://stackoverflow.com/a/7058373/5543597) or here (http://stackoverflow.com/a/213963/5543597) – John Lock May 22 '16 at 02:14
  • @JohnLock Yes, like i thought... Total rubbish – James May 22 '16 at 08:45

4 Answers4

7

optional doesn't work with polymorphic types. It's a value type, and polymorphic base classes don't work in an optional. Just as putting polymorphic base classes in vector or similar containers doesn't work.

Return a pointer. There's a reason why the advice is usually stated as "generally".

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • "Return a pointer" is very bad advice, because it needlessly introduces ownership issues. – Cheers and hth. - Alf May 22 '16 at 01:14
  • 1
    I don't understand the upvotes and selection as solution. The technical information given here, is incorrect. The advice is dubious at best. – Cheers and hth. - Alf May 22 '16 at 01:49
  • 1
    @Cheers I asked how to use std::optional with polymorphic type, and I got answer that it's not possible. – John Lock May 22 '16 at 02:20
  • @JohnLock: It's not **im**possible. By design, but unfortunately a large number of compilers don't support this, `boost::optional` can carry references. And the `std::optional` proposal has that as an [auxiliary proposal](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3672.html#rationale.refs). So the information given here that it's impossible and that `optional` is purely a value type where "polymorphic base class don't work", is incorrect. Still, practical considerations of portability strongly indicate using other solutions. – Cheers and hth. - Alf May 22 '16 at 02:29
  • 2
    @Cheersandhth.-Alf: "*"Return a pointer" is very bad advice, because it needlessly introduces ownership issues.*" I'm sorry, you may have confused "return a pointer" with "return a raw pointer." I said the former, not the latter. – Nicol Bolas May 22 '16 at 03:30
3

The std::optional proposal explicitly says it's not polymorphic:

value_ptr requires that the pointed-to object is allocated in the free store. This means that the sizeof(value_ptr<T>) is fixed irrespective of T. value_ptr is 'polymorphic': object of type value_ptr<T> can point to an object of type DT, derived from T. The deep copy preserves the dynamic type. optional requires no free store allocation: its creation is more efficient; it is not "polymorphic".

Your options boil down to raw pointer or unique_ptr. Use the unique_ptr if you need a copy and a raw pointer otherwise.

2

You can write a polymorphic pseudo-optional.

You'll want to implement small object optimization with a bounded object size/alignment (possibly parameters), and include a base class plus a set of additonal operations to erase down to (like copy or move). I have written a bounded polymorphic type without the pointer fallback that simply failed to compile if it lacked space for a similar reason, probably posted somewhere on SO.

Optional is not polymorphic, but regular and pseudo-regular value-types can be. Most of the arguments for optional over pointers/smart ptrs are actually arguments for using regular and pseudo-regular non-allocating types.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

I have read on stackoverflow in many posts, that when pointer is used(for argument or return value) and nullptr is allowed(making it optional), it is generally better to use std::optional instead.

Presumably you're referring to using std::optional to carry a value other than pointer or reference. In which case you're talking about an optional in-argument or an optional return value.


Using optional for optional in-argument has the disadvantage that the object is copied, or at least moved, which can be undesirable. Also, it precludes polymorphism.

The simplest way to do it, which also avoids unnecessary copying, and supports polymorphism, is to use a pointer to const:

void foo( Some_type const* p_object )

But then a caller will have to use the & operator on the actual argument.

To simplify calls you can provide overloads as syntactic sugaring:

void foo() { foo( nullptr ); }
void foo( Some_type const& object ) { foo( &object ); }

which supports calls like

foo();

and

foo( o );

Using optional for a return value has the advantage that exception throwing can be avoided when the calling code checks properly for value presence. But it appears to preclude ordinary RVO (return value optimization) for the wrapped object – weasel word “appears” because I can't see an airtight proof of this, but still I can't see how RVO could be done. So when the case of no return value is rare, and/or efficiency is very important, it might be better to just throw an exception. C++ only supports (raw) pointers and references for polymorphic return values, and there is no difference in this respect between using optional and throwing an exception to indicate no return value.

For a polymorphic optional return value you can use a smart pointer like std::unique_ptr or std::shared_ptr, that handles the ownership.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • "*I can't see how RVO could be done.*" `optional` isn't a magic type. So long as the caller receives the return value as an actual `optional`, then it will be subject to RVO. What isn't subject to RVO are more complex constructs like `T t = foo().value_or(some_default);` – Nicol Bolas May 22 '16 at 03:36
  • @NicolBolas: It doesn't appear that you grasped the issue, which is how RVO can be done for the wrapped object. – Cheers and hth. - Alf May 22 '16 at 04:20
  • 1
    Really, it all depends on how the function returning the `optional` uses it, just like any other form of elision. `optional` has an in-place construction facility, which would permit `return optional(in_place, ...)` to elide the copy/move. If you're returning a `T`, then obviously the copy cannot be elided. My point being that you can code in such a way that will not break elision. – Nicol Bolas May 22 '16 at 04:50
  • 2
    @NicolBolas: You're right. I had to think pretty long about it to see how to implement it (if I were a compiler), which might mean that I'm far too coffee-deprived. Fixing both caffeine levels & answer text, thanks! – Cheers and hth. - Alf May 22 '16 at 05:02