-1

Languages such as Scala and Java have had for quite some time some sort of optional type. In both, there seems to be a constructor method allowing one to either pass in an object O or a null value. The first constructs an optional value with a copy of O inside it, the second, an empty option.

From what I can gather, such a thing is not possible with std::optional<>(v) and std::make_optional<>(v), as both throw a runtime exception when called with a nullptr argument.

Is there any other constructor function allowing me to do this in the standard library?

Thanks

devoured elysium
  • 101,373
  • 131
  • 340
  • 557
  • 2
    `std::optional` stores an object (optionally) not a pointer. – 463035818_is_not_an_ai Nov 22 '21 at 13:42
  • 2
    Sounds like you are looking for `std::nullopt` – NathanOliver Nov 22 '21 at 13:44
  • @Scheff'sCat My question is not how to create an empty optional -- it's how to create an optional value out of a possibly nullable argument. – devoured elysium Nov 22 '21 at 13:47
  • 1
    what is a "possibly nullable argument" ? The "null" for `std::optional` is not `nullptr` but `nullopt` – 463035818_is_not_an_ai Nov 22 '21 at 13:49
  • 1
    @It's perfectly fine once the value type allows initialization from `nullptr`. Such as `auto os = std::make_optional>(nullptr);`. Live demo: https://godbolt.org/z/rh3GsGa4c. – Daniel Langr Nov 22 '21 at 13:50
  • @DanielLangr that code in gcc with C++ 20 throws a runtime exception. – devoured elysium Nov 22 '21 at 13:51
  • @devouredelysium Sorry, we cannot create a `std::string` from `nullptr`. Corrected. – Daniel Langr Nov 22 '21 at 13:53
  • 2
    Can you add some desired behavior to your question? That should help us understand a little better what you want, since C++ doesn't really have nullable objects. All objects in C++ have a value. Even a null pointer has a value of `0`, and that is treated as a special pointer value. – NathanOliver Nov 22 '21 at 13:53
  • 3
    Not every object in C++ is "nullable". Based on the behavior you're requesting, I'd argue that every `std::optional` is nullable, and that null value is `std::nullopt`. If you want all of your variables in C++ to be "nullable", then use `std::optional` for all of them. – JohnFilleau Nov 22 '21 at 13:59
  • Other options to make your things universally nullable are to use `std::shared_ptr` for everything, as that has a nullable concept (empty `std::shared_ptr`), or for every return type where you care about the nullable property, define in your own application space a special value that counts as null, and check for that every time you care. These are crazy though. When I write a function that may or may not return a usable value, I use nullptrs if the function would normally return a pointer type, or I use std::optional otherwise. Those two combined get me all I need. – JohnFilleau Nov 22 '21 at 14:03
  • 1
    Can you explain *why* you need to use `nullptr`? Why `std::nullopt` doesn't work for you? `std::nullopt` is a canonical way to return an empty `std::optional`. – JohnFilleau Nov 22 '21 at 14:07
  • If the construction of the type in question accepts a null pointer to begin with, it should either work fine or not compile. Please post a [mcve]. – molbdnilo Nov 22 '21 at 14:11
  • In C++, `std::optional` has a constructor method allowing one to either pass in an object `O` or a `std::nullopt` value. Very similar to Scala and Java. – Eljay Nov 22 '21 at 14:18
  • @Eljay if one has a `std::optional foo` where `T` can be initialized with a `nullptr`, I'd assume that `if (foo)` and `if(foo.value() == nullptr)` *should* signal different things. Otherwise, there's no reason to have the `std::optional`. `nullptr` here then doesn't signal the "nullable" version of `std::optional`. It's still only `std::nullopt`. – JohnFilleau Nov 22 '21 at 14:22
  • The comment is too old now, and I can't edit it. Only delete. I actually did mean to @molbdnilo – JohnFilleau Nov 22 '21 at 14:31
  • Note that the only "nullable" types in C++ are pointers (unless you also count the null optional). Types that accept a pointer as a construction argument may throw if that argument is the null pointer. Java's concept of converting null references to the empty optional does not make sense in C++. – molbdnilo Nov 22 '21 at 14:42
  • Java terminology and C++ terminology differ on the semantics of *references*. Java references(Java terminology) are pointers(C++ terminology), and that implementation detail can still be seen in Java's `NullPointerException`. Historical footnote: Scala was released in 2004. C++ had Boost Optional in 2003. – Eljay Nov 22 '21 at 14:51

3 Answers3

4

Since you didn't showed any code example, I will assume the following:

  • You have a function called f
  • The function f takes a potentially null int* as parameter (the nullable argument)
  • The function f returns a std::optional<int>
  • I will assume you have a similar function body in your codebase

Edit your question to add a more relevant examples if my assumptions are wrong.


So to recap the code example:

auto f(int* ptr) -> std::optional<int> {
    return std::optional<int>{*ptr};
}

The problem don't lie in std::optional and its contructors, but in the fact that you dereference a null pointer. This happen before any constructor call. At this point, you're already in UB land, you cannot infer behaviour after this happens.

The fix is to check the pointer before constructing the optional value:

auto f(int* ptr) -> std::optional<int> {
    return ptr ? std::optional<int>{*ptr} : std::nullopt;
}

Now, the int you send to the std::optional constructor is always valid assuming ptr is valid or null, and you return an empty optional if not.

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
2

An important property of std::optional is to not involve dynamic allocations. The object is (optionally) stored directly in the std::optional.

A std::optional<T> cannot hold a nullptr when T cannot have a nullptr value.

A std::optional can be constructed empty by calling the appropriate constructor:

// either
std::optional<int> x;                // does not contain an int
// or 
std::optional<int> y{std::nullopt};  // does not contain an int
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Hi. That's not what I asked. Please look at https://stackoverflow.com/a/4692607/130758. – devoured elysium Nov 22 '21 at 13:50
  • @devouredelysium meanwhile I understood that this is not what you asked, but I dont understand what you do ask. Please clarify the question. In Java, anything can be `null` but not in C++, hence your analogy is not sufficient to know what you are looking for – 463035818_is_not_an_ai Nov 22 '21 at 13:51
  • 2
    @devouredelysium I have no understanding of that language. Can you create a small C++ example that will do what this is supposed to do? Or at least an example that triggers the error you're experiencing? – Guillaume Racicot Nov 22 '21 at 14:18
2

Maybe you want std::nullopt, it signifies that optional doesn't hold value, in other words optional + nullopt is exactly what null means in other lanugages. In other words std::optional + std::nullopt inroduce natural sence of null into C++, same as in other languages.

Through std::nullopt you can control optional arguments in the function, like I did below for add() function.

You just check if (optional_value) to find out if it holds value, which is same as if (optional_value.has_value()). See doc here.

Also you can use .value_or(default) to return value or default, it is same as saying var or default or var if var is not None else default in Python for None-able argument.

You can also get value of optional just by *optional_value dereference, same like pointer. Doc here.

See all 4 possibilities of using nullable optional in implementation of add() function, it has 4 ways of doing same thing, choose which is better for you.

Try it online!

#include <optional>
#include <iostream>

int add(int x, std::optional<int> y = std::nullopt) {
    return x + y.value_or(5);
    // also possible to do same like this
    return x + (y ? y.value() : 5);
    // or same as
    return x + (y ? *y : 5);
    // or same as
    if (y)
        return x + *y;
    else
        return x + 5;
}

int main() {
    std::cout << add(3) << std::endl;
    std::cout << add(3, std::nullopt) << std::endl;
    std::cout << add(3, 7) << std::endl;
}

Output:

8
8
10

In other words if you want any variable to hold both value of some type and also null, then just wrap it into std::optional, and use std::nullopt to signify null, like in following example:

SomeClass obj; // non-nullable, can't be null
std::optional<SomeClass> obj2; // almost same as above but now is nullable
obj2 = obj; // you can naturally assign value of object
obj2 = std::nullopt; // this way you set variable to null
obj = *obj2; // this way you get value of object by using * dereference
obj = obj2.value(); // same as above instead of *
obj = obj2 ? *obj2 : default_value; // this way you check if obj2 is null, if not then get it's value through *, otherwise return default
obj = obj2.value_or(default_value); // same as last line above
if (obj2) DoSomething(); // checks if object is not null
if (obj2.has_value()) DoSomething(); // same as above

See all docs about std::optional.

Arty
  • 14,883
  • 6
  • 36
  • 69