// bslalg_nothrowmovableutil.h -*-C++-*- #ifndef INCLUDED_BSLALG_NOTHROWMOVABLEUTIL #define INCLUDED_BSLALG_NOTHROWMOVABLEUTIL #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide a wrapper that asserts a noexcept move constructor. // //@CLASSES: // bslalg::NothrowMovableUtil: utilities for 'bslalg_nothrowmovablewrapper' // //@SEE_ALSO: bslalg_nothrowmovablewrapper // //@DESCRIPTION: This component provides a utility struct, // 'bslalg::NothrowMovableUtil', for managing 'bslalg_nothrowmovablewrapper' // objects. It provides a namespace for static functions 'wrap' and 'unwrap' // with a uniform interface such that unwrapping an object that is not wrapped // or wrapping an object that is already wrapped are no-ops. This utility // struct also provides type traits for determining whether a type is wrapped // and for deducing the type of the wrapped and unwrapped object. // ///Usage ///----- // ///Example ///- - - - // In this example, we define a class template, 'CountedType<TYPE>', a wrapper // around 'TYPE' that counts the number of extant 'CountedType' objects. We // begin by defining the static count member along with the single value // member: //.. // template <class TYPE> // class CountedType { // // CLASS DATA // static int s_count; // // // DATA // TYPE d_value; //.. // Because of externally-imposed requirements, the move constructor for // 'CountedType' must provide the strong guarantee; i.e., if the move // constructor of 'TYPE' throws an exception, then the moved-from 'CountedType' // object must be left unchanged. To support this requirement, we next define // a private static function, 'MoveIfNoexcept', similar to the standard // 'std::move_if_noexcept', that returns a movable reference if its argument is // no-throw move constructible and a const lvalue reference otherwise: //.. // // PRIVATE CLASS FUNCTIONS // template <class TP> // static typename // bsl::conditional<bsl::is_nothrow_move_constructible<TP>::value, // bslmf::MovableRef<TP>, const TP&>::type // MoveIfNoexcept(TP& x); //.. // We next finish out the class definition with a constructor, copy // constructor, move constructor, destructor, and member functions to retrieve // the count and value: //.. // public: // // CLASS FUNCTIONS // static int count() { return s_count; } // // // CREATORS // CountedType(const TYPE& val); // // Construct 'CountedType' from the specified 'val'. // // CountedType(const CountedType& original); // // Copy construct '*this' from the specified 'original' object. // // CountedType(bslmf::MovableRef<CountedType> original); // // Move construct '*this' from 'original'. If an exception is // // thrown, by the constructor for 'TYPE' 'original' is unchanged. // // ~CountedType() { --s_count; } // // Destroy this object. // // // MANIPULATORS // TYPE& value() { return d_value; } // // // ACCESSORS // const TYPE& value() const { return d_value; } // }; //.. // Next, we implement 'MoveIfNoexcept', which calls 'move' on its argument, // allowing it to convert back to an lvalue if the return type is an lvalue // reference: //.. // template <class TYPE> // template <class TP> // inline typename // bsl::conditional<bsl::is_nothrow_move_constructible<TP>::value, // bslmf::MovableRef<TP>, const TP&>::type // CountedType<TYPE>::MoveIfNoexcept(TP& x) // { // return bslmf::MovableRefUtil::move(x); // } //.. // Next, we implement the value constructor and copy constructor, which simply // copy their argument into the 'd_value' data members and increment the count: //.. // template <class TYPE> // CountedType<TYPE>::CountedType(const TYPE& val) : d_value(val) // { // ++s_count; // } // // template <class TYPE> // CountedType<TYPE>::CountedType(const CountedType& original) // : d_value(original.d_value) // { // ++s_count; // } //.. // We're now ready implement the move constructor. Logically, we would simply // move the value from 'original' into the 'd_value' member of '*this', but an // exception thrown by 'TYPE''s move constructor would leave 'original' in a // (valid but) unspecified state, violating the strong guarantee. Instead, we // move the value only if we know that the move will succeed; otherwise, we // copy it. This behavior is facilitated by the 'MoveIfNoexcept' function // defined above: //.. // template <class TYPE> // CountedType<TYPE>::CountedType(bslmf::MovableRef<CountedType> original) // : d_value( // MoveIfNoexcept(bslmf::MovableRefUtil::access(original).d_value)) // { // ++s_count; // } //.. // Finally, we define the 's_count' member to complete the class // implementation: //.. // template <class TYPE> // int CountedType<TYPE>::s_count = 0; //.. // To test the 'CountedType' class template, assume a simple client type, // 'SomeType' that makes it easy to detect if it was move constructed. // 'SomeType' holds an 'int' value that is set to -1 when it is moved from, as // shown here: //.. // class SomeType { // int d_value; // public: // SomeType(int v = 0) : d_value(v) { } // IMPLICIT // SomeType(const SomeType& original) : d_value(original.d_value) { } // SomeType(bslmf::MovableRef<SomeType> original) // : d_value(bslmf::MovableRefUtil::access(original).d_value) // { bslmf::MovableRefUtil::access(original).d_value = -1; } // // int value() const { return d_value; } // }; //.. // Notice that 'SomeType' neglected to declare its move constructor as // 'noexcept'. This might be an oversight or it could be an old class that // predates both 'noexcept' and the 'bsl::is_nothrow_move_constructible' trait. // It is even be possible that the move constructor might throw (though, of // course, it doesn't in this simplified example). Regardless, the effect is // that move-constructing a 'CountedType<SomeType>' will result in the move // constructor actually performing a copy: //.. // void main() // { // CountedType<SomeType> obj1(1); // CountedType<SomeType> obj2(bslmf::MovableRefUtil::move(obj1)); // assert(1 == obj1.value().value()); // Copied, not moved from // assert(1 == obj2.value().value()); //.. // For the purpose of this example, we can be sure that 'SomeThing' will not // throw on move, at least not in our application. In order to obtain the // expected move optimization, we next wrap our 'SomeType in a // 'bslalg::NothrowMovableWrapper': //.. // CountedType<bslalg::NothrowMovableWrapper<SomeType> > // obj3(SomeType(3)); // CountedType<bslalg::NothrowMovableWrapper<SomeType> > // obj4(bslmf::MovableRefUtil::move(obj3)); // assert(-1 == obj3.value().unwrap().value()); // moved from // assert(3 == obj4.value().unwrap().value()); // } //.. // // Note that, in the last two lines of 'main', we must call 'unwrap' in order // to access the 'SomeType' object inside of the 'NothrowMovableWrapper'. This // is one situation where it would be attractive to have an overloadable // "operator dot" so that both 'CountedThing' and 'NothrowMovableWrapper' could // be transparent proxy types. C++ does not have overloadable operator dot, // but we can create a 'CountedType' that is more intelligent about the // existence of 'NothrowMovableWrapper' and automatically unwraps values for // the user's convenience. // // Rather than starting from scratch, we'll build our new counted type, // 'CountedType2' on 'CountedType'. We start be defining a single data member // of type 'CountedType': //.. // template <class TYPE> // class CountedType2 { // CountedType<TYPE> d_data; //.. // Next, for convenience, we add a public data type, 'ValueType' for the value // stored within 'CountedType2'. However, rather than defining 'ValueType' as // simply 'TYPE', we want to know if it is an instantiation of // 'NothrowMovableWrapper<TP>'. If it is, we want a type that represents the // unwrapped 'TP' rather than the full 'TYPE'. For this type transformation, // we turn to the type traits defined in 'bslalg::NothrowMovableUtil': //.. // public: // // TYPES // typedef typename // bslalg::NothrowMovableUtil::UnwrappedType<TYPE>::type ValueType; //.. // Note that the 'UnwrappedType' metafunction has no affect if 'TYPE' is not // wrapped. // // Next, we declare (and define) the class functions, constructors, and // destructor, simply forwarding to the corresponding 'CountedType' function, // constructor, or destructor: //.. // // CLASS FUNCTIONS // static int count() { return CountedType<TYPE>::count(); } // // // CREATORS // CountedType2(const TYPE& val) : d_data(val) { } // CountedType2(const CountedType2& original) // : d_data(original.d_data) { } // CountedType2(bslmf::MovableRef<CountedType2> original) // : d_data(bslmf::MovableRefUtil::move( // bslmf::MovableRefUtil::access(original).d_data)) { } //.. // Finally, we implement the 'value()' members such that the returned values do // not need to be unwrapped. As in the case of the 'UnwrappedType' // metafunction, the 'unwrap()' function in 'NothrowMovableUtil' handles both // wrapped and unwrapped arguments, unwrapping the latter and returning an // unmodified reference to the former: //.. // // MANIPULATORS // ValueType& value() // { // return bslalg::NothrowMovableUtil::unwrap(d_data.value()); // // Alternatively: 'return d_data.value();' // } // // // ACCESSORS // const ValueType& value() const // { // return bslalg::NothrowMovableUtil::unwrap(d_data.value()); // // Alternatively: 'return d_data.value();' // } // }; //.. // Note the alternative code for these members: A 'NothrowMovableWrapper<TP>' // object is implicitly convertible to 'TP&', so if 'TYPE' is a // 'NothrowMovableWrapper', the simple return statement will implicitly unwrap // it. // // Using a similar example for 'CountedType2' as we used for 'CountedType', we // see that the usage of 'CountedType2' with and without // 'NothrowMovableWrapper' is the same: //.. // void main() // { // CountedType2<SomeType> obj1(1); // CountedType2<SomeType> obj2(bslmf::MovableRefUtil::move(obj1)); // assert(1 == obj1.value().value()); // Copied, not moved from // assert(1 == obj2.value().value()); // // CountedType2<bslalg::NothrowMovableWrapper<SomeType> > // obj3(SomeType(3)); // CountedType2<bslalg::NothrowMovableWrapper<SomeType> > // obj4(bslmf::MovableRefUtil::move(obj3)); // assert(-1 == obj3.value().value()); // moved from // assert(3 == obj4.value().value()); // No need to call 'unwrap' // } //.. #include <bslscm_version.h> #include <bslalg_nothrowmovablewrapper.h> #include <bslmf_assert.h> #include <bslmf_conditional.h> #include <bslmf_isarray.h> #include <bslmf_isfunction.h> #include <bslmf_movableref.h> #include <bslmf_removecv.h> namespace BloombergLP { namespace bslalg { // FORWARD DECLARATION template <class TYPE, bool = bsl::is_function<TYPE>::value> struct NothrowMovableUtil_Traits; // ========================= // struct NothrowMovableUtil // ========================= struct NothrowMovableUtil { // Namesapace for 'NothrowMovableWrapper' traits and utilities. // TRAITS template <class TYPE> struct IsWrapped : NothrowMovableUtil_Traits<TYPE>::IsWrapped { // Metafunction evaluating to 'true_type' if 'TYPE' is a specialization // of 'NothrowMovableWrapper'; otherwise, 'false_type'. }; template <class TYPE> struct WrappedType { // Metafunction: If 'TYPE' is a specialization of // 'NothrowMovableWrapper', then 'WrappedType<TYPE>::type' is 'TYPE'; // otherwise 'WrappedType<TYPE>::type' is // 'NothrowMovableWrapper<TYPE>'. typedef typename NothrowMovableUtil_Traits<TYPE>::WrappedType type; }; template <class TYPE> struct UnwrappedType { // Metafunction: If 'TYPE' is a specialization of // 'NothrowMovableWrapper', then 'Unwrapped<TYPE>::type' is // 'TYPE::ValueType'; otherwise 'Unwrapped<TYPE>::type' is 'TYPE'. typedef typename NothrowMovableUtil_Traits<TYPE>::UnwrappedType type; }; // CLASS METHODS template <class TYPE> static typename UnwrappedType<TYPE>::type& unwrap(TYPE& f); template <class TYPE> static typename UnwrappedType<TYPE>::type const& unwrap(TYPE const& f); template <class TYPE> static bslmf::MovableRef<const typename UnwrappedType<TYPE>::type> unwrap(BSLMF_MOVABLEREF_DEDUCE(const TYPE) f); template <class TYPE> static bslmf::MovableRef<typename UnwrappedType<TYPE>::type> unwrap(BSLMF_MOVABLEREF_DEDUCE(TYPE) f); // Return a reference to the object wrapped in the specified 'f' // object. If 'f' is not wrapped, simply return a reference to 'f'. Note // that the overloads taking an lvalue argument prevent the overload // taking an rvalue argument from treating the argument as a forwarding // reference. The return type is chosen so both the value category and // constness of the parameter are preserved even in the case of const // rvalue references. template <class TYPE> static typename WrappedType<TYPE>::type wrap(TYPE& f); template <class TYPE> static typename WrappedType<TYPE>::type wrap(TYPE const& f); template <class TYPE> static typename WrappedType<TYPE>::type wrap(BSLMF_MOVABLEREF_DEDUCE(const TYPE) f); template <class TYPE> static typename WrappedType<TYPE>::type wrap(BSLMF_MOVABLEREF_DEDUCE(TYPE) f); // Return a wrapped copy of the specified 'f' object. If 'f' is // already wrapped, return a simple copy of 'f' without wrapping it // again. Note that the overloads taking an lvalue argument prevent // the overload taking an rvalue argument from treating the argument as // a forwarding reference. }; } // close package namespace // ---------------------------------------- // class template NothrowMovableUtil_Traits // ---------------------------------------- namespace bslalg { template <class TYPE> struct NothrowMovableUtil_Traits<TYPE, false /* is_function */> { // Component-private class -- do not use. This specialization of traits is // for non-function types that are not wrapped. // Should not be instantiated on reference types or array types. Assert // for 'IsReference' needs to be commented out because in C++03 // 'NothrowMovableUtil_Traits' may be instantiated with a 'MovableRef' // while doing overload resolution for calls to 'wrap'/'unwrap'. // BSLMF_ASSERT(!bslmf::MovableRefUtil::IsReference<TYPE>::value); BSLMF_ASSERT(!bsl::is_array<TYPE>::value); //TYPES typedef bsl::false_type IsWrapped; typedef TYPE UnwrappedType; typedef NothrowMovableWrapper<TYPE> WrappedType; }; template <class TYPE> struct NothrowMovableUtil_Traits<TYPE, true /* is_function */> { // Component-private class -- do not use. This specialization of traits is // for function types that are not wrapped. This specialization is NOT for // function pointers or function references. // // 'UnwrappedType' is an object type, and is thus the decayed version of // 'TYPE', i.e., a function pointer. Since 'unwrap' always returns a // reference, this decay means that, 'unwrap(f)' will not compile if 'f' is // a function or reference to function. However 'UnwrappedType pf = f;' // will work whether 'f' is a function or not. // TYPES typedef bsl::false_type IsWrapped; typedef TYPE *UnwrappedType; typedef NothrowMovableWrapper<TYPE *> WrappedType; }; template <class TYPE> struct NothrowMovableUtil_Traits<NothrowMovableWrapper<TYPE>, false> { // Component-private class -- do not use. This specialization is for // wrapped types. // TYPES typedef bsl::true_type IsWrapped; typedef TYPE UnwrappedType; typedef NothrowMovableWrapper<TYPE> WrappedType; }; } // close package namespace // ============================================================================ // TEMPLATE AND INLINE IMPLEMENTATIONS // ============================================================================ // ----------------------------------------- // struct template NothrowMovableUtil // ----------------------------------------- // PUBLIC CLASS METHODS template <class TYPE> inline typename bslalg::NothrowMovableUtil::UnwrappedType<TYPE>::type& bslalg::NothrowMovableUtil::unwrap(TYPE& f) { return f; } template <class TYPE> inline typename bslalg::NothrowMovableUtil::UnwrappedType<TYPE>::type const& bslalg::NothrowMovableUtil::unwrap(TYPE const& f) { return f; } template <class TYPE> inline bslmf::MovableRef< const typename bslalg::NothrowMovableUtil::UnwrappedType<TYPE>::type> bslalg::NothrowMovableUtil::unwrap(BSLMF_MOVABLEREF_DEDUCE(const TYPE) f) { const typename bslalg::NothrowMovableUtil::UnwrappedType<TYPE>::type& r = bslmf::MovableRefUtil::access(f); return bslmf::MovableRefUtil::move(r); } template <class TYPE> inline bslmf::MovableRef< typename bslalg::NothrowMovableUtil::UnwrappedType<TYPE>::type> bslalg::NothrowMovableUtil::unwrap(BSLMF_MOVABLEREF_DEDUCE(TYPE) f) { typename bslalg::NothrowMovableUtil::UnwrappedType<TYPE>::type& r = bslmf::MovableRefUtil::access(f); return bslmf::MovableRefUtil::move(r); } template <class TYPE> inline typename bslalg::NothrowMovableUtil::WrappedType<TYPE>::type bslalg::NothrowMovableUtil::wrap(TYPE& f) { return f; } template <class TYPE> inline typename bslalg::NothrowMovableUtil::WrappedType<TYPE>::type bslalg::NothrowMovableUtil::wrap(const TYPE& f) { return f; } template <class TYPE> inline typename bslalg::NothrowMovableUtil::WrappedType<TYPE>::type bslalg::NothrowMovableUtil::wrap(BSLMF_MOVABLEREF_DEDUCE(const TYPE) f) { return typename WrappedType<TYPE>::type(bslmf::MovableRefUtil::move(f)); } template <class TYPE> inline typename bslalg::NothrowMovableUtil::WrappedType<TYPE>::type bslalg::NothrowMovableUtil::wrap( BSLMF_MOVABLEREF_DEDUCE(TYPE) f) { return typename WrappedType<typename bslmf::MovableRefUtil::RemoveReference< TYPE>::type>::type(bslmf::MovableRefUtil::move(f)); } } // close enterprise namespace #endif // ! defined(INCLUDED_BSLALG_NOTHROWMOVABLEUTIL) // ---------------------------------------------------------------------------- // Copyright 2020 Bloomberg Finance L.P. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ----------------------------- END-OF-FILE ----------------------------------