0

I have a optional member in a class, which I want to return by value, via a method. Sample code:

#include <stdio.h>
#include <optional>
#include <iostream>
using namespace std;

class bar {
public:
    int a;

    bar(const bar &obj) {
        a = obj.a;
    }
};

class foo {
public:
    void init(){
        abc->a = 100;
    }

    optional<bar> get() {
        return abc;
    }
    
    optional<bar> abc;
};

int main()
{
    foo temp;
    temp.init();
    auto copied = temp.get();
    cout << "Expected value is 100, got: " << copied->a;
    return 0;
}

The code outputs some garbage value.

How may I achieve this?


From my understanding, optional stores a fully allocated memory for the underlying type (not just a reference), and while returning a optional variable, the copy constructor of the underlying type should kick in, which should copy the memory as-is to the new optional value being returned.

Nishant
  • 2,571
  • 1
  • 17
  • 29
  • 3
    The default constructor of `std::optional` creates an *empty* optional (contains no value), so your `foo` constructor `abc->a = 100;` invokes *undefined behavior* (as you have no object to access) – UnholySheep Sep 10 '20 at 07:33
  • What matters is whether an object exists inside the memory or not; that there’s room for one isn’t enough. – molbdnilo Sep 10 '20 at 07:39
  • @UnholySheep How to add assign a value to the optional? `member_.value() = newValue`? – Nishant Sep 10 '20 at 07:41
  • @UnholySheep @molbdnilo I updated the sample code to initialize the underlying type via a separate `init` call and invoked the `init` api in the main function. Now, the underlying type should be initialized properly to be copied over, right? – Nishant Sep 10 '20 at 07:46
  • No, `value()` throws an exception if the optional does not contain a value. You either construct the optional with a value (using the corresponding constructor overload) or you use the overloaded `operator=`, or the member function `emplace` (if you want to create the object in-place) – UnholySheep Sep 10 '20 at 07:46
  • The `bar` class is not instantiable as is, since the only constructor it provides is a copy constructor. You should add at least a default constructor or a converting constructor from `int`. – Daniel Langr Sep 10 '20 at 07:59

2 Answers2

4

You need to use the constructor of optional to ensure that it contains an object: (assuming that the redundant copy constructor of bar, which blocks it from being constructed, is removed)

foo()
    : abc{bar{100}}
{
}

or, after the optional has been created:

void init(){
    abc = bar{100};
}

Otherwise, the optional is kept in an empty state, and invoking -> on an empty optional results in undefined behavior. The copy constructor of optional does not copy construct a contained object when the source is empty.

L. F.
  • 19,445
  • 8
  • 48
  • 82
  • I made some edits to my question. Basically I cannot initialize the optional member in the constructor, so I added an init method to do that. How would that change this answer? – Nishant Sep 10 '20 at 07:48
  • @Nishant use assignment instead. `emplace` works too. See update. – L. F. Sep 10 '20 at 07:48
  • Note that `bar` would need to have a suitable converting constructor to make this work. – Daniel Langr Sep 10 '20 at 07:52
  • @L.F. I do need a default constructor for bar, does not work otherwise. – Nishant Sep 10 '20 at 07:54
  • @Nishant Oops, overlooked that. Right now there is no (legit) way to construct an object of type `bar`, as far as I can tell, so `optional` won't help you with that. – L. F. Sep 10 '20 at 07:56
  • 1
    @DanielLangr indeed, I neglected that. `bar` can't be used as-is anyway. – L. F. Sep 10 '20 at 07:57
1

std::optional is like a smart pointer, it defaults to a "null" value and accessing its members would therefore be undefined behaviour. You need to initialise it before use:

void init(){
  abc = bar();
  abc->a = 100;
}

Note that as it stands bar isn't constructible other than via the copy constructor so you need to add a default constructor or a constructor that takes an int argument or remove the unnecessary copy constructor.

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60