0

I'm refactoring some legacy code which reads some binary data from a file into struct. It occurred to me that changing the variable to a std::optional could ensure the variable actually read (initialized) before it is used. But the file reading code needs the address of the variable. Is there a way (an intended way not a hack) to tell a std::optional "give me a pointer to your uninitialized T so I can write the contents of that memory" and "go ahead and change your state from empty to value-ful now"?

(That is, instead of assigning a default T (what if T has no default constructor) and then taking the address of the default constructed value.)

Simplified example:

struct FILE_HEADER { /* some data members... */ };

class FileFrobulator
{
private:
   FILE_HEADER fileHead;

public:
   void called_first(IFileReader* reader)
   {
       // ...
       reader->read(&fileHead, sizeof(FILE_HEADER));
       // ...
   }

   void called_later(IFileReader* reader)
   {
      // ...
      // use fileHead.foo, fileHead.bar, etc. while reading rest of file
      // ...
   }
};

The question is if I change the member to std::optional<FILE_HEADER> fileHead; what then do I change for the line that currently reads reader->read(&fileHead, sizeof(FILE_HEADER));?

I could do this:

fileHead = FILE_HEADER();
reader->read(&*fileHead, sizeof(FILE_HEADER));

You may have objected earlier that a feature to take the address of an uninitialized T in a std::optional and set the optional as no longer empty runs the risk of accidentally leaving the optional marked value-ful while still uninitialized if the user doesn't in fact write the memory. However note that the code example above runs a similar albeit lesser risk: if reader->read() throws, the optional will no longer be empty, but it also isn't valid for use. Default constructed T is arguably better than uninitialized T, however if FILE_HEADER is a C struct (which in this case it is) it's members are still uninitialized!

Maybe this is better:

FILE_HEADER temp;
reader->read(&temp, sizeof(FILE_HEADER));
fileHead = temp;

But both of those involve a redundant initialization and/or copy (perhaps optimized-out by the compiler).

Dennis
  • 2,607
  • 3
  • 21
  • 28
  • You have to construct the FILE_HEADER instance, in your `std::optional`. Maybe by using the `emplace()` method. Until you do that, it does not exist, and attempting to scribble something into where you think it should be will, at best, do nothing, and at worst a crash. – Sam Varshavchik Mar 19 '20 at 15:22

1 Answers1

0

Is there a way (an intended way not a hack) to tell a std::optional "give me a pointer to your uninitialized T so I can write the contents of that memory" and "go ahead and change your state from empty to value-ful now"?

Both of those things would be a lie.

An empty optional<T> doesn't have a T, initialized or not, so you'd be getting a pointer/reference to an object that doesn't exist. And the state of an optional<T> is supposed to reflect whether a T exists within the optional<T>. Since copying bits by itself doesn't cause a T to spring into existence, unless the user directly creates a T, it won't exist. So pretending that a T exists when it doesn't is also false.

To do the kind of thing you're intending, you should just have a function which returns an optional<FILE_HEADER>:

fileHead = readFileHeader(reader);

If the reading function throws, then the value in FileFrobulator never gets set. You could even make this a lambda function, if you so desire:

fileHead = [](IFileReader* reader) {
  std::optional<FILE_HEADER> ret(std::in_place);
  reader->read(&*ret, sizeof(FILE_HEADER));
  return ret;
}(reader);
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Probably `&*ret` you meant? – Red.Wave Mar 19 '20 at 16:32
  • "An empty optional doesn't have a T, initialized or not" - it has space for a T, doesn't it? – user253751 Mar 19 '20 at 16:49
  • @Red.Wave: No; it's supposed to be returning an `optional`; in the event of an exception, `fileHead` will remain empty. Also, returning a reference to a destroyed object (like `&*ret` does) isn't helpful. – Nicol Bolas Mar 19 '20 at 17:18
  • @user253751: "*it has space for a T, doesn't it?*" That doesn't mean it *has* one. – Nicol Bolas Mar 19 '20 at 17:19
  • @NicolBolas Well, the asker wants to construct a T in that space. – user253751 Mar 19 '20 at 17:40
  • 1
    @user253751: At no point does the OP say that he wants to "construct" the `T`. If he did, he'd just use the existing API for that: `optional::emplace`. He specifically wants to *bypass* construction by accessing the memory directly without a `T` existing first, copying some bits in, and then fooling the `optional` into thinking that the memory contains a `T` now. – Nicol Bolas Mar 19 '20 at 17:41
  • That brings up an interesting point. Does it have space though? I mean it probably has space, but is it required to? (I.e. is an implementation or specialization of optional which heap allocates on-demand compliant with the standard?) Then you have the case where a specialization borrows unused bits to indicate empty status (which I believe *is* allowed such as for pointers). Does it "have" the space if it's oversubscribing the bits? – Dennis Mar 19 '20 at 18:11
  • I appreciate that your answer clued me in to std::in_place. – Dennis Mar 19 '20 at 18:23
  • Plz disambiguate the syntax error at the end of your post. What did you mean by `&*filehead`? – Red.Wave Mar 19 '20 at 19:07
  • I think he meant `&*ret` inside the lambda. – Dennis Mar 19 '20 at 19:34
  • @Dennis: "*I mean it probably has space, but is it required to?*" Yes. – Nicol Bolas Mar 19 '20 at 19:42