Another example showing how AI output should be carefully reviewed, and not accepted passively.
I was “discussing” with Claude about using #define guards vs. #pragma once for C++ header files. At some point, Claude wrote that:
If your library is only ever going to target modern mainstream compilers (which, realistically, is true for almost everyone today), plain #pragma once is fine and is what most modern C++ style guides (including Google’s and LLVM’s) now recommend by default.
I did remember that, last time I checked, Google’s C++ Style Guide required #define guards (not#pragma once) for header files. That is still true as of today (August 18th, 2026); quoting Google’s C++ Style Guide on this point:
All header files should have #define guards to prevent multiple inclusion. The format of the symbol name should be <PROJECT>_<PATH>_<FILE>_H_.
So I made my point to Claude, and it replied apologizing for its error:
You’re right, and I apologize for the error. Looking at the actual page, Google’s C++ Style Guide explicitly requires #define guards, not #pragma once
This is another example showing how AI output cannot be 100% trusted, and should always be carefully reviewed.
Google’s C++ Style Guide requiring #define guards for header files.An excerpt of my “discussion” with Claude AI on #define guards vs. #pragma once.
On AI Hallucinations
I briefly asked another AI (Gemini) about AI hallucinations (like the previous example with Claude), and it interestingly pointed out that:
AI models predict the next most likely word based on patterns in data. They do not have real-world awareness or a true concept of “truth.”
That is something to keep in mind when dealing with AI.
This is a concrete real-world example showing how AI-generated results should not be 100% completely trusted.
Recently I asked Claude to review the code of my WinReg C++ library (which is a C++ high-level wrapper around the low-level C-interface Windows Registry API).
As a result of its analysis, Claude reported that there were zero-length bugs in my code, in particular Claude stated that zero-length REG_SZ/REG_EXPAND_SZ values crash the GetStringValue, GetExpandStringValue, TryGetStringValue and TryGetExpandStringValue methods of the RegKey class.
In particular, Claude noted that I correctly guarded against dataSize == 0 in the binary-returning getters (like RegKey::GetBinaryValue), but the string getters do not have such guard; they unconditionally do:
result.resize((dataSize / sizeof(wchar_t)) - 1);
Claude specified that REG_SZ and REG_EXPAND_SZ values can be legitimately stored with cbData == 0 (zero bytes, no NUL at all; different from an empty string made by a single NUL ‘\0’).
If you substitute zero for the dataSize variable in the above statement, you end up with:
result.resize(SIZE_MAX);
which would throw a std::length_error exception.
Claude proposed to fix the above code using the same edge-case check logic I had already implemented in the binary getters to guard against the zero-length case:
My WinReg library is quite battle-tested, and there were bugs related to some edge cases that I had already fixed, so I was curious, and tried writing a zero-length string value in the registry, and read it back with my existing code. And I noted that (at least in Windows 11 where I tested my code) the dataSize == 0 condition was not hit at run-time.
That is because in my C++ code I invoke the RegGetValue(W) API, which by contract guarantees to return a NUL-terminated string, even if the string stored in the registry doesn’t have a NUL-terminator. (This is not the case for older APIs like RegQueryValueEx.)
I replied to Claude pointing that out, and Claude corrected itself:
You’re right, and thanks for the pushback — I should have accounted for RegGetValueW‘s null-termination guarantee before flagging that as a bug.
Those AI tools can be very powerful, but the key takeway here is that we should not forget that they are just tools, and we should not trust AI-generated output and code 100%, because bugs and wrong assumptions can be hidden in that AI code, too.
In VS Code, selecting the C++ language standard is not as intuitive as one would expect.
I have been using Visual Studio for C++ development since it was still called Visual C++ (and was a 100% C++-focused IDE), starting from version 4 (maybe 4.2) on Windows 95. I loved VC++ 6. Even today, Microsoft Visual Studio is still my first choice for C++ development on Windows.
In addition to that, I wanted to use VS Code for C++ development for some course work. Why choosing VS Code? Well, in addition to being free to use (as is the Visual Studio Community Edition), another important point of VS Code in that teaching context is its cross-platform feature: in fact, it’s available not only for Windows, but also for Linux and Mac, and students using those platforms could easily follow along.
I had VS Code and the MS C/C++ extension already installed on one of my PCs. I wrote some C++ demo code that used some C++20 features. I tried to build that code, and I got some error messages, telling me that I was using features that required at least C++20. Fine, I thought: Maybe the default C++ standard is set to something pre-C++20 (for example, VS 2019 defaults to C++14).
So, I pressed Ctrl+Shift+P, selected C/C++ Edit Configurations (UI), and in the C/C++ Configurations page, selected c++20 for the C++ standard.
Then I pressed F5 to start a debugging session, preceded by a build process, and saw that the build process failed.
I took a look at the error message in the terminal window, and to my surprise the error messages were telling me that some libraries (like <span>) were available only with C++20 or later. But I had just selected the C++20 standard a few minutes ago!
So, I double-checked, pressing Ctrl+Shift+P and selecting C/C++ Edit Configurations (UI), and in the C/C++ Configurations, the selected C++ standard was c++20, as expected.
C++20 selected in the MS C/C++ Extension Configurations UI
I also took a look at the c_cpp_properties.json, and found that the “cppStandard” property was properly set to “c++20”, as well.
C++20 selected in the c_cpp_properties.json
Despite these confirmations in the UI, I noted that in the terminal window, on the command line used to build the C++ source code, the option to set the C++20 compilation mode was notpassed to the C++ compiler!
Surprisingly, the option for the C++ language standard was not passed on the command line
So, basically, the UI was telling me that the C++20 mode was enabled. But the C++ compiler was invoked in a way that did not reflect that, as the flag enabling C++20 was not specified on the command line!
I also tried to close and reopen VS Code, double-checked things one more time, but the results were always the same: C++20 was set in the C/C++ Configurations UI and in the c_cpp_properties.json file, but compilation failed due to the C++20 option not specified on the command line when invoking the C++ compiler.
After some time, to my surprise, I noted that the issue was closed as “by design”! Seriously? I mean, what kind of good reasonable intuitive design is the one in which the UI tells you that you have selected a given C++ language standard, but the command line doesn’t compile your code according to that??
This is “by design”. The settings in c_cpp_properties.json do not affect the build. You need to set the flags in your tasks.json or other source of build info (CMakeLists.txt etc.).
So, am I supposed to manually set the C++20 flag in the tasks.json, despite having already set it in the C/C++ Configurations UI? Well, I do think that is either a bug, or a bad and confusing design choice. If I set the C++20 option in the UI, that should be automatically reflected on the command line, as well. If a modification is required to tasks.json to enable C++20, that should have been the job of the UI, in which I had already selected the C++20 standard!
Compare that to the sane intuitive behavior of Visual Studio, in which you can simply set the C++ standard option in the UI, and the IDE will invoke the C++ compiler with the proper flags, reflecting that.
Selecting the C++ Language Standard in Visual Studio 2019
Let’s see how to fix a common problem when building mixed C++/C# projects in Visual Studio.
Someone had a Visual Studio 2019 solution containing a C# application project and a native C++ DLL project. The C# application was supposed to call some C-interface functions exported by the native C++ DLL.
Both projects built successfully in Visual Studio. But, after the C# application was launched, a System.DllNotFoundException was thrown when the C# code tried to invoke the DLL-exported functions:
Visual Studio complains about an unhandled System.DllNotFoundException when debugging the C# application.
So, it looks like the C# application is unable to find the native C++ DLL.
First Attempt: A Manual Fix
In a first attempt to solve this problem, I tried manually copying the native C++ DLL from the folder where it was built into the same folder where the C# application was built (for example: from MySolution\Debug to MySolution\CSharpApp\bin\Debug). Then, I relaunched the C# application, and everything worked fine as expected this time! Wow 🙂 The problem was kind of easy to fix.
However, I was not 100% happy, as this was kind of a manual fix, that required manually copying-and-pasting the DLL from its own folder to the C# application folder. I would love to have Visual Studio doing that automatically!
A Better Solution: Making the Copy Automatic in Post-build
Well, it turns out that we can do better than that! In fact, it’s possible to automate that process and basically instruct Visual Studio’s build system to perform the aforementioned copy for us. To do so, we basically need to specify a custom command line that VS automatically executes as a post-build event.
In Solution Explorer, right-click on the C# application project, and select Properties from the menu.
Select the Build Events tab, and enter the desired copy instruction in the Post-build event command line box. For example, the following command can be used:
Then type Ctrl+S or click the Save button (= diskette icon) in the toolbar to save these changes.
Setting the post-build event command line to copy the DLL into the C# application folder
Basically, with the above settings we are telling Visual Studio: “Dear VS, after successfully building the C# application, please copy the C++ DLL from its original folder into the same folder where you have just built the C# application. Thank you very much!”
After relaunching the C# application, this time everything went well, and the C# EXE was able to find the C++ DLL and call its exported C-interface functions.
Addendum: Demystifying the $(Thingy)
If you take a look at the custom copy command added in post-build event, you’ll notice some apparently weird syntax like $(SolutionDir) or $(TargetDir). These $(something) are basically MSBuild macros, that expand to meaningful stuff like the path of the Visual Studio solution, or the directory of the primary output file for the build (e.g. the directory where the C# application .exe file is created).
Note that the macros representing paths can include the trailing backslash \; for example, this is the case of $(SolutionDir). So, take that into account when combining these macros to refer to actual sub-directories and paths in your solution.
$(SolutionDir) represents the full path of the Visual Studio solution, e.g. C:\Users\Gio\source\repos\MySolution\.
$(TargetDir) is the directory of the primary output file for the build, for example the directory where the C# console app .exe is created. This could be something like C:\Users\Gio\source\repos\MySolution\CSharpApp\bin\Debug\.
$(Configuration) is the name of the current project configuration, for example: Debug when doing a debug build.
So, for example: $(SolutionDir)$(Configuration) would expand to something like C:\Users\Gio\source\repos\MySolution\Debug in debug builds.
In addition, you can also see how these MSBuild macros are actually expanded in a given context. To do so in Visual Studio, once you are in the Build Events tab, click the Edit Post-build button. Then click the Macros > > button to view the actual expansions of those macros.
Let’s bust a myth that is a source of many subtle bugs. Are you sure that you can simply drop UTF-8-encoded text in char-based strings that expect ASCII text, and your C++ code will still work fine?
Several (many?) C++ programmers think that we should use UTF-8 everywhere as the Unicode encoding in our C++ code, stating that UTF-8 is a simple easydrop-in replacement for existing code that uses ASCII char-based strings, like const char* or std::string variables and parameters.
Of course, that UTF-8-simple-drop-in-replacement-for-ASCII thing is wrong and just a myth!
In fact, suppose that you wrote a C++ function whose purpose is to convert a std::string to lower case. For example:
// Code proposed by CppReference:
// https://en.cppreference.com/w/cpp/string/byte/tolower
//
// This code is basically the same found on StackOverflow here:
// https://stackoverflow.com/q/313970
// https://stackoverflow.com/a/313990 (<-- most voted answer)
std::string str_tolower(std::string s)
{
std::transform(s.begin(), s.end(), s.begin(),
// wrong code ...
// <omitted>
[](unsigned char c){ return std::tolower(c); } // correct
);
return s;
}
Well, that function works correctly for pure ASCII characters. But as soon as you try to pass it a UTF-8-encoded string, that code will not work correctly anymore! That was already discussed in my previous blog post, and also in this post on The Old New Thing blog.
I’ll give you another simple example. Consider the following C++ function, PrintUnderlined(), that receives a std::string (passed by const&) as input, and prints it with an underline below:
// Print the input text string, with an underline below
void PrintUnderlined(const std::string& text)
{
std::cout << text << '\n';
std::cout << std::string(text.length(), '-') << '\n';
}
For example, invoking PrintUnderlined(“Hello C++ World!”), you’ll get the following output:
Hello C++ World!
----------------
Well, as you can see, this function works fine with ASCII text. But, what happens if you pass UTF-8-encoded text to it?
Well, it may work as expected in some cases, but not in others. For example, what happens if the input string contains non-pure-ASCII characters, like the LATIN SMALL LETTER E WITH GRAVE è (U+00E8)? Well, in this case the UTF-8 encoding for “è” is represented by two bytes: 0xC3 0xA8. So, from the viewpoint of the std::string::length() method, that “single character è” counts as twochars. So, you’ll get two underscore characters for the single è, instead of the expected one underscore character. And that will produce a bogus output with the PrintUnderlined function! And note that this same function works correctly for ASCII char-based strings.
So, if you have some existing C++ code that works with const char* or std::string, or similar char-based string types, and assumes ASCII encoding for text, don’t expect to pass a UTF-8-encoded strings and have it just automagically working fine! The existing code may still compile fine, but there is a good chance that you could have introduced subtle runtimebugs and logic errors!
Spend some time thinking about the exact type of encoding of the const char* and std::string variables and parameters in your C++ code base: Are they pure ASCII strings? Are these char-based strings encoded in some particular ANSI/Windows code pages? Which code page? Maybe it’s an “ANSI” Windows code page like Latin 1 / Western European Windows-1252 code page? Or some other code page?
You can pack many different kinds of stuff in char-based strings (ASCII text, text encoded in various code pages, etc.), and there is no guarantee that code that used to work fine with that particular encoding would automatically continue to work correctly when you pass UTF-8-encoded text.
If we could start everything from scratch today, using UTF-8 for everything would certainly be an option. But, there is a thing called legacy code. And you cannot simply assume that you can just drop UTF-8-encoded strings in the existing char-based strings in existing legacy C++ code bases, and that everything will magically work fine. It may compile fine, but running fine as expected is another completely different thing.
Use STL string objects like std::string/std::wstring as a safe bridge.
Last time, we saw that passing a C++ std::[w]string_view to a C-interface API (like Win32 APIs) expecting a C-stylenull-terminatedstring pointer can cause subtle bugs, as there is a requirement impedance mismatch. In fact:
The C-interface API (e.g. Win32 SetWindowText) expects a null-terminated string pointer
The STL string views do not guarantee null-termination
So, supposing that you have a C++17 (or newer) code base that heavily uses string views, when you need to interface those with Win32 API function calls, or whatever C-interface API, expecting C-style null-terminated strings, how can you safely pass instances of string views as input parameter?
Invoking the string_view/wstring_view’s data method would be dangerous and source of subtle bugs, as the data returned pointer is not guaranteed to point to a null-terminated string.
Instead, you can use a std::string/wstring object as a bridge between the string views and the C-interface API. In fact, the std::string/wstring’s c_str method does guarantee that the returned pointer points to a null-terminated string. So it’s safe to pass the pointer returned by std::[w]string::c_str to a C-interface API function that expects a null-terminated C-style string pointer (like PCWSTR/LPCWSTR parameters in the Win32 realm).
For example:
// sv is a std::wstring_view
// C++ STL strings can be easily initialized from string views
std::wstring str{ sv };
// Pass the intermediate wstring object to a Win32 API,
// or whatever C-interface API expecting
// a C-style *null-terminated* string pointer.
DoSomething(
// PCWSTR/LPCWSTR/const wchar_t* parameter
str.c_str(), // wstring::c_str
// Other parameters ...
);
// Or use a temporary string object to wrap the string view
// at the call site:
DoSomething(
// PCWSTR/LPCWSTR/const wchar_t* parameter
std::wstring{ sv }.c_str(),
// Other parameters ...
);
How to *properly* convert Unicode strings to lower and upper cases in C++? Unfortunately, the simple common char-by-char conversion loop with tolower/toupper calls is wrong. Let’s see how to fix that!
Back in November 2017, on my previous MS MVPs blog, I wrote a post criticizing what was a common but wrong way of converting Unicode strings to lower and upper cases.
Basically, it seems that people started with code available on StackOverflow or CppReference, and wrote some kind of conversion code like this, invoking std::tolower for each char/wchar_t in the input string:
That kind of code would be safe and correct for pure ASCII strings. But even if you consider Unicode UTF-8-encoded strings, that code would be totally wrong.
Very recently (October 7th, 2024), a blog post appeared on The Old New Thing blog, discussing how that kind of conversion code is wrong:
Besides the copy-and-pasto of using std::tolower instead of std::towlower for wchar_ts, there are deeper problems in that kind of approach. In particular:
You cannot convert in a context-free manner like that wchar_t-by-wchar_t, as context involving adjacent wchar_ts can indeed be important for the conversion.
You cannot assume that the result string has the same size (“length” in wchar_ts) as the input source strings, as that is in general not true: In fact, there are cases where to-lower/to-upper strings can be of different lengths than the original strings.
As I wrote in my old 2017 article (and stated also in the recent Old New Thing blog post), a possible solution to properly convert Unicode strings to lower and upper cases in Windows C++ code is to use the LCMapStringEx Windows API. This is a low-level C interface API.
I wrapped it in higher-levelconvenient reusable C++ code, available here on GitHub. I organized that code as a header-only library: you can simply include the library header, and invoke the ToStringLower and ToStringUpper helper functions. For example:
#include "StringCaseConv.hpp" // the library header
std::wstring name;
// Simply convert to lower case:
std::wstring lowerCaseName = ToStringLower(name);
The ToStringLower and ToStringUpper functions take std::wstring_view as input parameters, representing views to the source strings. Both functions return std::wstring instances on success. On error, C++ exceptions are thrown.
There are also overloaded forms of these functions that accept a locale name for the conversion.
The code compiles cleanly with VS 2019 in C++17 mode with warning level 4 (/W4) in both 64-bit and 32-bit builds.
Note that the std::wstring and std::wstring_view instances represent Unicode UTF-16 strings. If you need strings represented in another encoding, like UTF-8, you can use conversion helpers to convert between UTF-16 and UTF-8.
P.S. If you need a portable solution, as already written in my 2017 article, an option would be using the ICU library with its icu::UnicodeString class and its toLower and toUpper methods.
Thank you for the suggestion. But *in that context* that would cause nasty bugs in my code, and in code that relies on it.
…Because (in the given context) that would be wrong 🙂
This “suggestion” comes up with some frequency…
The context is this: I have some Win32 C++ code that takes input string parameters as const std::wstring&, and someone suggests me to substitute those wstring const reference parameters with string viewslike std::wstring_view. This is usually because they have learned from someone in some course/video course/YouTube video/whatever that in “modern” C++ code you should use string views instead of passing string objects via const&. [Sarcastic mode on]Are you passing a string via const&? Your code is not modern C++! You are such an ignorant C++98 old-style C++ programmer![Sarcastic mode off] 😉
(There are also other “gurus” who say that in modern C++ you should always use exceptions to communicate error conditions. Yeah… Well, that’s a story for another time…)
So, Thank you for the suggestion, but using std::wstring_view instead of const std::wstring& in that context would introduce nasty bugs in my C++ code (and in other people’s code that relies on my own code)! So, I won’t do that!
// Input string passed via const&.
//
// Someone suggests me to replace 'const wstring &'
// with wstring_view:
//
// void DoSomething(std::wstring_view s, ...)
//
void DoSomething(const std::wstring& s, ...)
{
// This API expects input string as PCWSTR,
// i.e. _null-terminated_ const wchar_t*.
SomeWin32Api(s.data(), ...); // <-- See the P.S. later
}
std::wstringguarantees that the pointer returned by the wstring::data() method points to a null-terminated string.
On the other hand, invoking std::wstring_view::data() does not guarantee that the returned pointer points to a null-terminated string. It may, or may not. But there is no guarantee!
So, since [w]string_views are not guaranteed to be null-terminated, using them with Win32 APIs that expect null-terminated strings is totally wrong and a source of nasty bugs.
So, if your target are Win32 API calls that expect null-terminated C-style strings, just keep passing good old std::wstring by const&.
P.S. Invoking data() vs. c_str() – To make things clearer (and more bug-resistant), when you need a null-terminated C-style string pointer as input parameter, it’s better to invoke the c_str() method on [w]string (instead of the data() method), as there is no corresponding c_str() method available with [w]string_view.
In this way, if someone wants to “modernize” the existing C++ code and tries to change the input string parameter from [w]string const& to [w]string_view, they get a compiler error when the c_str() method is invoked in the modified code (as there is no c_str() method available for string views). It’s much better to get a compile-time error than a subtle run-time bug!
On the other hand, the data() method is available for both strings and string views, but its guarantees about null-termination are different for strings vs. string views.
So, invoking the string’s c_str() method (instead of the data() method) is what I suggest when passing STL strings to Win32 API calls that expect C-style null-terminated string pointers as input (read-only) parameters. I consider this a best practice.
(Of course, if the C-interface API function needs to write to the provided string buffer, the data() method must be invoked, as it’s overloaded for both the const and non-const cases.)
Pay attention to the target of the address-of (&) operator!
Suppose that you have some data stored in a std::vector, and you need to pass it to a function that takes a pointer to the beginning of the data, and in addition the data size or element count.
Something like this:
// Input data to process
std::vector<int> myData = { 11, 22, 33 };
//
// Do some processing on the above data
//
DoSomething(
??? , // beginning of data
myData.size() // element count
);
You may think of using the address-of operator (&) to get a pointer to the beginning of the data, like this:
But the above code is wrong. In fact, if you use the address-of operator (&) with a std::vector instance, you get the address of the “control block” of std::vector, that is the block that contains the three pointers first, last, end, according to the model discussed in a previous blog post:
Taking the address of a std::vector (&v) points to its control block
Luckily, if you try the above code, it will fail to compile, with a compiler error message like this one produced by the Visual C++ compiler in VS 2019:
Error C2664: 'void DoSomething(const int *,size_t)':
cannot convert argument 1
from 'std::vector<int,std::allocator<int>> *' to 'const int *'
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
What you really want here is the address of the vector’s elements stored in contiguous memory locations, and pointed to by the vector’s control block.
To get that address, you can invoke the address-of operator on the first element of the vector (which is the element at index 0): &v[0].
// This code works
DoSomething(&myData[0], myData.size());
As an alternative, you can invoke the std::vector::data method:
DoSomething(myData.data(), myData.size());
Now, there’s a note I’d like to point out for the case of empty vectors:
If size() is 0, data() may or may not return a null pointer.
CppReference.com
I would have preferred a well-defined behavior such that, when size is 0 (i.e. the vector is empty), data() must return a null pointer (nullptr). This is the good behavior that is implemented in the C++ Standard Library that comes with VS 2019. I believe the C++ Standard should be fixed to adhere to this intelligent behavior.
An example of writing clear code with good intention, but getting an unexpected C++ compiler error.
Someone asked me for help with their C++ code. The code was something like this:
std::wstring something;
std::optional<bool> result = something.empty() ?
ReadBoolValueFromRegistry() : {};
The programmer who wrote this code wanted to check whether the ‘something’ string was empty, and if it was, a boolean value had to be read from the Windows registry, and then stored into the std::optionalresult variable.
On the other hand, if the ‘something’ string was not empty, the std::optional result should be default-initialized to an empty optional (i.e. an optional that doesn’t contain any value).
That was the programmer’s intention. Unfortunately, their code failed to compile with Visual Studio 2019 (C++17 mode was enabled).
The offending C++ code with squiggles under the {
There were squiggles under the opening brace {, and the Visual C++ compiler emitted the following error messages:
Error Code
Description
C2059
syntax error: ‘{‘
C2143
syntax error: missing ‘;’ before ‘{‘
I was asked: “What’s the problem here? Are there limitations of using the {} syntax to specify nothing?”
This is a good question. So, clearly, the C++ compiler didn’t interpret the {} syntax as a way to default-initialize the std::optional in case the string was not empty (i.e. the second “branch” in the conditional ternary operator).
A first step to help the C++ compiler figuring out the programmer’s intention could be to be more explicit. So, instead of using {}, you can try and use the std::nullopt constant, which represents an optional that doesn’t store any value.
// Attempt to fix the code: replace {} with std::nullopt
std::optional<bool> result = something.empty() ?
ReadBoolValueFromRegistry() : std::nullopt;
Unfortunately, this code doesn’t compile either.
Why is that?
Well, to figure that out, let’s take a look at the C++ conditional operator (?:). Consider the conditional operator in its generic form:
// C++ conditional operator ?:
exp1 ? exp2 : exp3
In the above code snippet, “exp2” is represented by the ReadBoolValueFromRegistry call. This function returns a bool. So, in this case the return type of the conditional operator is bool.
// Attempt to fix the code: replace {} with std::nullopt
std::optional<bool> result = something.empty() ?
ReadBoolValueFromRegistry() : std::nullopt;
// ^^^--- exp2 ^^^--- exp3
// Type: bool
On the other hand, if you look at “exp3”, you see std::nullopt, which is a constant of type std::nullopt_t, not a simple bool value!
So, you have this kind of type mismatch, and the C++ compiler complains. This time, the error message is:
Error C2446 ‘:’: no conversion from ‘const std::nullopt_t’ to ‘bool’
So, to fix that code, I suggested to “massage” it a little bit, like this:
// Rewrite the following code:
//
// std::optional<bool> result = something.empty() ?
// ReadBoolValueFromRegistry() : {};
//
// in a slightly different manner, like this:
//
std::optional<bool> result{};
if (something.empty()) {
result = ReadBoolValueFromRegistry();
}
Basically, you start with a default-initialized std::optional, which doesn’t contain any value. And then you assign the bool value read from the registry only if the particular condition is met.
The above C++ code compiles successfully, and does what was initially in the mind of the programmer.
P.S. I’m not a C++ “language lawyer”, but it would be great if the C++ language could be extended to allow the original simple code to just work:
std::optional<bool> result = something.empty() ?
ReadBoolValueFromRegistry() : {};