27

All the std::make_ are made redundant by C++17 with the introduction of Class template argument deduction (except make_unique and make_shared).

So what is the point of std::make_optional? As far as I can tell it does the exact same thing as the deduction guides for std::optional.

Is there a scenario where std::make_optional is preferred over deduction guides?

bolov
  • 72,283
  • 15
  • 145
  • 224
  • "*except `make_unique` and `make_shared`, they are still needed for reasons of leak free memory*" - that is not why those functions exist. They exist to create `unique_ptr`/`shared_ptr` objects *more efficiently* (especially `shared_ptr`) than creating them with raw pointers passed to their constructors. – Remy Lebeau Jul 05 '20 at 03:56
  • 1
    @RemyLebeau correct, I didn't realised that with C++17 you can't have interleaved parameter evaluations so it can't leak anymore. – bolov Jul 05 '20 at 18:31
  • @RemyLebeau Leak freedom is precisely why those functions exist. If you write `void func(std::unique_ptr, std::unique_ptr);` and then call it `func(std::unique_ptr(new int(42)), std::unique_ptr(new int(13));`, you can get a memory leak, because the compiler is allowed to make calls in this order: `new`, `new`, `unique_ptr ctor`, `unique_ptr ctor`. If the second `new` throws, the first allocation is leaked. If you call `func(std::make_unique(42), std::make_unique(13));`, the calls to `new` and the calls to the `unique_ptr` ctor can not get separated. – Bulletmagnet Mar 09 '21 at 22:49
  • 1
    @Bulletmagnet since C++17 that sequence is not allowed anymore. Iirc the order of the function arguments is implementation specific, but with that order each argument evaluation is sequenced before the next. – bolov Mar 09 '21 at 23:21

2 Answers2

21

One example of the difference is when you want (for whatever reason) to make an optional containing an optional:

#include <optional>
#include <type_traits>

int main()
{
    auto inner=std::make_optional(325);
    auto opt2=std::make_optional(inner); // makes std::optional<std::optional<int>>
    auto opt3=std::optional(inner);      // just a copy of inner
    static_assert(std::is_same_v<decltype(opt2), std::optional<std::optional<int>>>);
    static_assert(std::is_same_v<decltype(opt3), std::optional<int>>);
}
Ruslan
  • 18,162
  • 8
  • 67
  • 136
  • 9
    [three star programmer](https://wiki.c2.com/?ThreeStarProgrammer) - `std::optional` edition. Good catch. – bolov Jul 04 '20 at 22:44
  • 1
    It is unfortunate that the library solution is needed for generic code because the core language chose a default that is convenient for the non-generic case. – Davis Herring Jul 04 '20 at 23:45
  • 2
    @DavisHerring you wanted `std::optional a{}; auto x = std::optional{a}` to be `std::optional>` and `std:vector v1{}; auto v2 = std::vector{v1}` to be `std::vector>`? And what would `std::string s1{}; auto s2 = std::string{s1}` be? – bolov Jul 05 '20 at 00:27
  • @bolov How is he a three star programmer?? I do not see any stars in his code?? – Yunfei Chen Jul 05 '20 at 00:28
  • 2
    @YunfeiChen it was an analogy to the linked joke. In C if you have pointer of pointer of pointer you would need to write `***p` to access the hold object. If you have optional of optional of optional to access the held object you would do `***o`. ... it's a joke. – bolov Jul 05 '20 at 01:06
  • 2
    @bolov The fact that `vector{v1}` and `vector{v1, v1}` are both valid and wildly different types is, I think, a mistake. `string{s1}` isn't the same kind of thing at all, since `string` is actually a type. Bonus points for `tuple{x}` being one of three different things dependent on `x`... – Barry Jul 05 '20 at 01:35
  • @bolov Or more so that `vector{x}` is either a vector containing 1 element (`x`) or a copy of `x`, depending on `x`. – Barry Jul 05 '20 at 01:43
  • 1
    @bolov: Yes; it’s a common (though surely not universal) opinion that “wrap by default” would have been better than “copy by default” because the latter can already be written as just `auto x=a;` without harming genericity. – Davis Herring Jul 05 '20 at 02:15
  • @bolov I was joking, I was pointing out how his code doesn't have any * or pointers of the sort and yet you still called him a three star programming ***chuckling*. – Yunfei Chen Jul 05 '20 at 03:11
  • @Barry very good points. My personal peeve is list intialization having the same syntax as a constructor. But now that I think about it that would solve the vector and tuple problem, but not the optional one. – bolov Jul 05 '20 at 06:56
2

Another example of a use for std::make_optional() would be for constructing the object stored in a std::optional without creating a temporary for it when its constructor takes multiple arguments.

For example, consider the following Point3D class whose constructor has multiple arguments:

struct Point3D {
   Point3D(int xx, int yy, int zz): x(xx), y(yy), z(zz) {}
   int x, y, z;
};

Imagine that you want to initialize the object stored by a std::optional<Point3D> to Point3D(1, 2, 3)X, then you could proceed as:

std::optional oPoint = Point3D(1, 2, 3);

However, this would create a Point3D temporary object that is then moved into the std::optional. Instead, by using the std::make_optional() convenience function template you can avoid the creation of that temporary:

auto oPoint = std::make_optional<Point3D>(1, 2, 3);

Since we are talking about C++17 here (i.e., std::optional was introduced in C++17), guaranteed copy elision applies here, and therefore no Point3D temporary object is created.


Note that you can still avoid the creation of the temporary object without using std::make_optional() by passing std::in_place to the constructor of the std::optional<Point3D> object:

std::optional<Point3D> oPoint{std::in_place, 1, 2, 3};

This will construct the stored Point3D object in place, thus avoiding the creation of a temporary. Nevertheless, you may find this more verbose than the approach with std::make_optional().


Xstd::optional<Point3D> oPoint(1, 2, 3); doesn't compile.

JFMR
  • 23,265
  • 4
  • 52
  • 76
  • "when its constructor takes multiple arguments"... how would this be different if the constructor took a single argument? – David Faure Dec 03 '21 at 22:28
  • @DavidFaure In that case you can simply pass the single argument the constructor takes directly to `std::optional`'s constructor. For example, for `struct Foo{ Foo(int){} };`, you can just construct a `std::option` with `std::optional optFoo(7);`. Note that this wouldn't compile if `Foo`'s constructor took multiple arguments as `std::in_place` would need to be passed to `std::option`'s constructor. – JFMR Dec 04 '21 at 11:33