Optional?
Agenda Type traits intro Interface & usage Caveats
Why? Uninitialized variables cause lots of bugs Removes much need for 2-stage initialization of objects A cheap 0 or 1 element container Internal storage. Much preferable over scoped_ptr + heap allocation Much less bloat & faster compilation than boost::optional
Type traits are meta functions! is_integer ::value => true is_floating_point ::value => false alignment_of ::value => 128 remove_reference ::type => T is_same ::value => false is_convertible ::value => true Very useful when writing generic code
Implemented with template specialization // Default template struct is_floating_point { enum { value = false; } }; template<> struct is_floating_point { enum { value = true; } }; template<> struct is_floating_point { enum { value = true; } };
Useful for static dispatch, among other things.. template T my_cool_algorithm(T t) { return my_cool_algorithm_impl ::value>(t); } template T my_cool_algorithm_impl(T t); template T my_cool_algorithm_impl(T t) { // int optimized version } template T my_cool_algorithm_impl(T t) { // float optimized version }
Optional interface struct Nothing {}; template class Optional { Optional(); Optional(Nothing); // Note implicit Optional(const T& value); // Note implicit Optional(const Optional & copy); Optional & operator=(Nothing); Optional & operator=(const T& value); Optional & operator=(const Optional & rhs); bool isSet() const; isSet() T& get(); const T& get() const; }; // Override placement new to allow constructing elements without copying template inline void* operator new(size_t, fb::Optional & optional); // Convenience function template const T& getValueOr(const Optional & optional, const T& defaultValue) const; template T& getValueOr(Optional & optional, T& defaultValue) const;
Basic usage Optional i; ASSERT(!i.isSet()); i = 23; ASSERT(i.isSet() && i.get()==23); i = Nothing(); ASSERT(!i.isSet());
Return from functions // C style bool sqrt(double n, double& result); Optional sqrt(double n); Client code won’t forget to check the bool All-or-nothing guarantee, can’t break output variables
Member variables not always valid class X { Optional m_resultCache; }; Clearer than using -1 or other "invalid values“
Single item container class X { Optional m_activeWeapon; }; Does not require DefaultConstructible objects No heap allocations! Uses placement new internally Useful to store RAII objects
Expressive interface class X { void setLookForEnemyAroundPosition( const Optional & position); const Optional & getLookForEnemyAroundPosition(); };
Simplifying code class Target { // Remove, use optional instead! bool isValid() const; }; void foo(Optional t); void bar(Target t); void baz(Target t); Often little code that need complexity of invalid objects Helps maintenance – expressive client code
Caveats in current implementation Aligned types double in size No implicit bool conversion Unnecessary branch for some types Most of them can be fixed simply
Aligned types double in size sizeof(Optional ) == 2 * sizeof(Vec3) For Vec3, the pad data can be used
No implicit bool conversion Missing safe implicit type conversion to bool if (Optional xRoot = sqrt(x))
Unnecessary branch for some types ~Optional() { if (!HasTrivialDestructor ::Value && m_initialized) m_storage.destruct(); } Will get C++0x features in all of our compilers soon Until then types can be registered as having a trivial, no- op destructor
Further reading Unit tests in OptionalTest.cpp Static tests in HasTrivialDestructor.cpp, AlignedStorage.cpp and Optional.cpp Design rationale for boost.optional html Nullable i C#