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?
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.
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. –
Barbate 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.
std::optional
. –
Mackey 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). –
Subgenus optional<double>
and move on. –
Flyman 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.
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? –
Barbate double
value that it's possible to produce? All you need is one pattern that doesn't. –
Barbate 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.
© 2022 - 2025 — McMap. All rights reserved.
double
is not guaranteed to have a size of 8. For example some platforms use a 4 bytedouble
. Perhaps the question should be worded assizeof(std::optional<double>) == sizeof(double)
. – Marnistd::isnan
instead ofhas_value
and you have what you want. – Peavy