Is it possible to implement std::optional such that sizeof(std::optional<double>) == 8
by somehow using that you can store characters in a NAN, see http://en.cppreference.com/w/cpp/numeric/math/nan? Are there implementations that do that? Can it be done in terms of the functionality that is available in the standard?

- 2,600
- 19
- 32
-
3`double` is not guaranteed to have a size of 8. For example [some platforms](https://www.arduino.cc/en/Reference/Double) use a 4 byte `double`. Perhaps the question should be worded as `sizeof(std::optional
) == sizeof(double)`. – François Andrieux Aug 04 '17 at 15:43 -
The question is, do you want to know if this can be done well in practice so that it is reliable on some platform? Or do you want to know if it can be done in a way that is 100% standard compliant? The answer is yes and no. – Nir Friedman Aug 04 '17 at 16:12
-
You can use `std::isnan` instead of `has_value` and you have what you want. – Daniel Aug 04 '17 at 16:14
-
@NirFriedman I guess I would like to know both. I was just wondering if this technique can be leveraged somehow. Are there implementations that do that? Is it possible in implementations that are based on the IEEE standard? – levzettelin Aug 04 '17 at 17:20
-
@TobiasBrüll We didn't do it in the contest of 'optional' but I've worked on a library which layered an "NA" value (similar to what R/pandas do) onto the primitives. For signed integer types it used the most negative value, and for floating point it used one of the quiet NaN bit patterns. So it's definitely practical, and it works well. But it can't be done by the standard, afaik there is no bit pattern that's illegal. Some are NaN, some are sub-normal, but they are all legal. – Nir Friedman Aug 04 '17 at 17:50
-
1You might be interested in [compact_optional](https://github.com/akrzemi1/compact_optional). – yuri kilochek Aug 13 '17 at 15:55
4 Answers
I don't think this can be done because there is no rule preventing programs from utilizing and relying on the extra bits in NaN on their own. Then if you store the magic number into the optional
it looks like it's not present instead of the application's special NaN.

- 95,107
- 10
- 109
- 188
-
What if the magic number was not a legal `double` value? There's no rule that says that every possible combination of bit values that can fit in a `double` must represent a legal `double` value. Fault bit patterns are allowed and if there's a reason `optional` couldn't use them, your answer doesn't explain what it is. – David Schwartz Aug 04 '17 at 16:52
Answer is multifold.
First of all, it can not be implemented with the functionality available in Standard, since Standard says nothing of floating point implementation.
Second, for IEEE 754 floating points you can implement your own optional by specializing std::optional
for doubles. However, this would mean that you exclude a valid value (NaN is a result produced by some arithmetic operations) from your range of values. However, diving deep into IEEE 754, you might choose a specific NaN representation (there are a lot of those!) as a no-value.

- 61,605
- 5
- 78
- 137
-
Doesn't the standard disallow specializing standard templates unless at least one argument is a user defined type? Also, the specialization would still be required to meet the requirements of `std::optional`. – eerorika Aug 04 '17 at 16:04
-
@user2079303 Yes it does. specializing `optional` for `double` would be UB. However, you can create your own type that contains a double, and implicitly converts to it and use that. Probably annoying in some situations though (template edge cases). – Nir Friedman Aug 04 '17 at 18:06
-
@user2079303 Well, at this time your code is not portable anyways, so I would not care too much about generic standard problems. It is prohibited simply because implementations are allowed to specialize (or overload in case of functions) for non-user-defined types, so it would be enough to verify your implementation doesn't do this for `optional
` and move on. – SergeyA Aug 04 '17 at 19:23
It is not possible to implement std::optional
like that because it contradicts the post-conditions that specify how the class (template) behaves. For example: std::optional
contains a value if it is initialized with a value of type T
, but your suggested std::optional<double>
would not contain a value if it was initialized with a value that is the special NaN value that you've chosen.
Also, the C++ standard does not guarantee/require that the floating point type supports (quiet) NaN. Some systems do not.
It is certainly possible to implement your own non-standard optional
class with different semantics. Of course, you will then be relying on the implementation defined fact that NaN values exist. You also have to rely on the knowledge of the floating point representation, because as far as I know, there are no standard utilites for inspecting the NaN payload - only for generating a value that has a specific payload.

- 232,697
- 12
- 197
- 326
-
What if the special NaN value was not a legal value for a `double` but still fits. For example, imagine a platform where every `double` had an extra "is a valid double" bit that was used internally only for debug purposes. Why couldn't `optional` co-opt this bit? – David Schwartz Aug 04 '17 at 16:51
-
@DavidSchwartz I suppose, if the system reserved this bit specifically for the compiler to do as it wishes. And I suppose the possibility is largely academical :) – eerorika Aug 04 '17 at 21:50
-
How sure are you that every possible bit pattern corresponds to a `double` value that it's possible to produce? All you need is one pattern that doesn't. – David Schwartz Aug 04 '17 at 22:52
Implementing what you propose is rather trivial, using a good text editor and cut-and-paste. Since it's a good idea, I've decided to add it to my tool box. My main motivation is that std::optional<>s are rather big, and thus not practical to use in std::variant<> types.
#include <type_traits>
#include <limits>
#include <exception>
class bad_optional_flt_access
: public std::exception
{
public:
bad_optional_flt_access() {}
const char* what() const noexcept override
{
return "bad optional float access";
}
};
template <typename Float, bool = std::is_floating_point<Float>::value>
class optional_flt;
template <typename Float>
class optional_flt<Float, false> {};
template <typename Float>
class optional_flt<Float, true>
{
public:
constexpr optional_flt() noexcept : value_(std::numeric_limits<Float>::quiet_NAN()) {}
constexpr optional_flt(const Float& val) noexcept : value_(val) {}
template<typename T>
constexpr optional_flt(const T& val) noexcept : value_(Float(val)) {}
constexpr bool has_value() const noexcept
{
return value_ != std::numeric_limits<Float>::quiet_NAN();
}
void reset() noexcept { value_ = std::numeric_limits<Float>::quiet_NAN(); }
constexpr void swap(optional_flt& other) noexcept { std::swap(value_, other.value_); }
constexpr operator bool() const noexcept { return has_value(); }
Float& value () &
{
if (!has_value())
throw bad_optional_flt_access();
return value_;
}
Float&& value () &&
{
if (!has_value())
throw bad_optional_flt_access();
return value_;
}
constexpr const Float& value () const &
{
if (!has_value())
throw bad_optional_flt_access();
return value_;
}
Float& operator * () & noexcept { return value_; }
constexpr const Float& operator * () const & noexcept{ return value_; }
template< class U >
constexpr Float value_or( U&& default_value ) const&
{
return (has_value()) ? value_ : default_value;
}
template< class U >
constexpr Float value_or( U&& default_value ) &&
{
return (has_value()) ? value_ : default_value;
}
private:
Float value_;
};
template< class T, class U >
constexpr bool operator==( const optional_flt<T>& lhs, const optional_flt<U>& rhs )
{
return lhs.value() == rhs.value();
}
template< class T, class U >
constexpr bool operator!=( const optional_flt<T>& lhs, const optional_flt<U>& rhs )
{
return lhs.value() != rhs.value();
}
template< class T, class U >
constexpr bool operator<( const optional_flt<T>& lhs, const optional_flt<U>& rhs )
{
return lhs.value() < rhs.value();
}
template< class T, class U >
constexpr bool operator<=( const optional_flt<T>& lhs, const optional_flt<U>& rhs )
{
return lhs.value() <= rhs.value();
}
template< class T, class U >
constexpr bool operator>( const optional_flt<T>& lhs, const optional_flt<U>& rhs )
{
return lhs.value() > rhs.value();
}
template< class T, class U >
constexpr bool operator>=( const optional_flt<T>& lhs, const optional_flt<U>& rhs )
{
return lhs.value() >= rhs.value();
}
template<typename T>
constexpr optional_flt<T> make_optional_flt(const T& x)
{
return optional_flt<T>(x);
}
int main()
{
int i = 2;
auto x = optional_flt<float>{i};
auto y = optional_flt<double>(2.5);
return (*x < .5) ? sizeof(optional_flt<double>) : 1;
}
The code above is gcc -std=c++11
, clang -std=c++14
, and cl /std:c++11
compatible.

- 6,338
- 1
- 15
- 19