diff --git a/src/misc/Makefile b/src/misc/Makefile index 0f717d8..48e6366 100644 --- a/src/misc/Makefile +++ b/src/misc/Makefile @@ -25,6 +25,7 @@ INC += pv/templateMeta.h INC += pv/current_function.h INC += pv/pvUnitTest.h INC += pv/reftrack.h +INC += pv/anyscalar.h LIBSRCS += byteBuffer.cpp LIBSRCS += bitSet.cpp diff --git a/src/misc/pv/anyscalar.h b/src/misc/pv/anyscalar.h new file mode 100644 index 0000000..97c6f33 --- /dev/null +++ b/src/misc/pv/anyscalar.h @@ -0,0 +1,322 @@ +#ifndef PV_ANYSCALAR_H +#define PV_ANYSCALAR_H + +#if __cplusplus>=201103L +# include +#endif + +#include +#include +#include + +#include + +#include +#include +#include /* for ScalarType enum */ + +namespace epics{namespace pvData{ +namespace detail { + +// special mangling for AnyScalar ctor to map from argument type to storage type. +// allow construction from constants. +template +struct any_storage_type { typedef T type; }; +template<> struct any_storage_type { typedef int32 type; }; +template<> struct any_storage_type { typedef uint32 type; }; +template<> struct any_storage_type { typedef std::string type; }; +template<> struct any_storage_type { typedef std::string type; }; + +}// namespace detail + +/** A type-safe variant union capable of holding + * any of the PVD scalar types (POD or string) + * + @code + AnyScalar A(5); + assert(A.type()==pvInt); + assert(A.ref()==5); + assert(A.as()==5); + assert(A.as()==5.0); + assert(A.ref()==5.0); // throws AnyScalar::bad_cast + @endcode + */ +class AnyScalar { +public: + struct bad_cast : public std::exception { +#if __cplusplus>=201103L + bad_cast() noexcept {} + virtual ~bad_cast() noexcept {} + virtual const char* what() const noexcept +#else + bad_cast() throw() {} + virtual ~bad_cast() throw() {} + virtual const char* what() const throw() +#endif + { return "bad_cast() type mis-match"; } + }; + +private: + ScalarType _stype; + + // always reserve enough storage for std::string (assumed worst case) +#if __cplusplus>=201103L + struct wrap_t { + typename std::aligned_storage::type blob[1]; + } _wrap; +#else + struct wrap_t { + union blob_t { + char data[sizeof(std::string)]; + double align_f; // assume std::string alignment <= 8 + } blob[1]; + } _wrap; +#endif + + // assumed largest non-string type + typedef double _largest_blob; + + template + inline T& _as() { + return *reinterpret_cast(_wrap.blob); + } + template + inline const T& _as() const { + return *reinterpret_cast(_wrap.blob); + } +public: + AnyScalar() : _stype((ScalarType)-1) {} + + template + explicit AnyScalar(T v) + { + typedef typename meta::strip_const::type T2; + typedef typename detail::any_storage_type::type TT; + + STATIC_ASSERT(sizeof(TT)<=sizeof(_wrap.blob)); + + new (_wrap.blob) TT(v); + + // this line fails to compile when type T can't be mapped to one of + // the PVD scalar types. + _stype = (ScalarType)ScalarTypeID::value; + } + + AnyScalar(const AnyScalar& o) + :_stype(o._stype) + { + if(o._stype==pvString) { + new (_wrap.blob) std::string(o._as()); + } else if(o._stype!=(ScalarType)-1) { + memcpy(_wrap.blob, o._wrap.blob, sizeof(_largest_blob)); + } + } + +#if __cplusplus>=201103L + AnyScalar(AnyScalar&& o) + :_stype(o._stype) + { + typedef std::string string; + if(o._stype==pvString) { + new (_wrap.blob) std::string(); + _as() = std::move(o._as()); + o._as().~string(); + } else if(o._stype!=(ScalarType)-1) { + memcpy(_wrap.blob, o._wrap.blob, sizeof(_largest_blob)); + } + o._stype = (ScalarType)-1; + } +#endif + + ~AnyScalar() { + if(_stype==pvString) { + typedef std::string string; + _as().~string(); + } + // other types need no cleanup + } + + AnyScalar& operator=(const AnyScalar& o) { + AnyScalar(o).swap(*this); + return *this; + } + + template + AnyScalar& operator=(T v) { + AnyScalar(v).swap(*this); + return *this; + } + +#if __cplusplus>=201103L + AnyScalar& operator=(AnyScalar&& o) { + if(_stype==pvString) { + typedef std::string string; + _as().~string(); + } + _stype = (ScalarType)-1; + swap(o); + return *this; + } +#endif + + void swap(AnyScalar& o) { + typedef std::string string; + switch((int)_stype) { + case -1: + switch((int)o._stype) { + case -1: + // nil <-> nil + break; + case pvString: + // nil <-> string + new (_wrap.blob) std::string(); + _as().swap(o._as()); + o._as().~string(); + break; + default: + // nil <-> non-string + memcpy(_wrap.blob, o._wrap.blob, sizeof(_largest_blob)); + break; + } + break; + case pvString: + switch((int)o._stype) { + case -1: + // string <-> nil + new (o._wrap.blob) std::string(); + _as().swap(o._as()); + _as().~string(); + break; + case pvString: + // string <-> string + _as().swap(o._as()); + break; + default: { + // string <-> non-string + std::string temp; + temp.swap(_as()); + + _as().~string(); + + memcpy(_wrap.blob, o._wrap.blob, sizeof(_largest_blob)); + + new (o._wrap.blob) std::string(); + temp.swap(o._as()); + } + break; + } + break; + default: + switch((int)o._stype) { + case -1: + // non-string <-> nil + memcpy(o._wrap.blob, _wrap.blob, sizeof(_largest_blob)); + break; + case pvString: { + // non-string <-> string + std::string temp; + temp.swap(o._as()); + + o._as().~string(); + + memcpy(o._wrap.blob, _wrap.blob, sizeof(_largest_blob)); + + new (_wrap.blob) std::string(); + temp.swap(_as()); + } + break; + default: + // non-string <-> non-string + _largest_blob temp; + memcpy(&temp, _wrap.blob, sizeof(_largest_blob)); + memcpy(_wrap.blob, o._wrap.blob, sizeof(_largest_blob)); + memcpy(o._wrap.blob, &temp, sizeof(_largest_blob)); + // std::swap(o._wrap.blob, _wrap.blob); // gcc <=4.3 doesn't like this + break; + } + break; + } + std::swap(_stype, o._stype); + } + + inline ScalarType type() const { + return _stype; + } + + void* unsafe() { return _wrap.blob; } + const void* unsafe() const { return _wrap.blob; } + + inline bool empty() const { return _stype==(ScalarType)-1; } + +#if __cplusplus>=201103L + explicit operator bool() const { return !empty(); } +#else +private: + typedef void (AnyScalar::*bool_type)(AnyScalar&); +public: + operator bool_type() const { return !empty() ? &AnyScalar::swap : 0; } +#endif + + /** Return reference to wrapped value */ + template + // T -> strip_const -> map to storage type -> add reference + typename detail::any_storage_type::type>::type& + ref() { + typedef typename meta::strip_const::type T2; + typedef typename detail::any_storage_type::type TT; + + if(_stype!=(ScalarType)ScalarTypeID::value) + throw bad_cast(); + return _as(); + } + + template + // T -> strip_const -> map to storage type -> add const reference + typename meta::decorate_const::type>::type>::type& + ref() const { + typedef typename meta::strip_const::type T2; + typedef typename detail::any_storage_type::type TT; + + if(_stype!=(ScalarType)ScalarTypeID::value) + throw bad_cast(); + return _as(); + } + + /** copy out wrapped value, with a value conversion. */ + template + T as() const { + typedef typename meta::strip_const::type T2; + typedef typename detail::any_storage_type::type TT; + + if(_stype==(ScalarType)-1) + throw bad_cast(); + TT ret; + castUnsafeV(1, (ScalarType)ScalarTypeID::value, &ret, + _stype, _wrap.blob); + return ret; + } + +private: + friend std::ostream& operator<<(std::ostream& strm, const AnyScalar& v); +}; + +inline +std::ostream& operator<<(std::ostream& strm, const AnyScalar& v) +{ + switch(v.type()) { +#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case pv ## PVACODE: strm<(); break; +#define CASE_REAL_INT64 +#define CASE_STRING +#include "pv/typemap.h" +#undef CASE +#undef CASE_REAL_INT64 +#undef CASE_STRING + default: + strm<<"(nil)"; break; + } + return strm; +} + +}} // namespace epics::pvData + +#endif // PV_ANYSCALAR_H diff --git a/testApp/misc/Makefile b/testApp/misc/Makefile index 9679904..2118b9b 100644 --- a/testApp/misc/Makefile +++ b/testApp/misc/Makefile @@ -80,3 +80,7 @@ TESTS += testjson TESTPROD_HOST += test_reftrack test_reftrack_SRCS += test_reftrack.cpp TESTS += test_reftrack + +TESTPROD_HOST += testanyscalar +testanyscalar_SRCS += testanyscalar.cpp +TESTS += testanyscalar diff --git a/testApp/misc/testanyscalar.cpp b/testApp/misc/testanyscalar.cpp new file mode 100644 index 0000000..b60f5e3 --- /dev/null +++ b/testApp/misc/testanyscalar.cpp @@ -0,0 +1,227 @@ + +#include + +#include + +#include +#include + +#include "pv/anyscalar.h" + +namespace pvd = epics::pvData; + +namespace { + +void test_empty() +{ + testDiag("test_empty()"); + pvd::AnyScalar O; + testOk1(O.empty()); + testOk1(!O); + + testThrows(pvd::AnyScalar::bad_cast, O.ref()); + testThrows(pvd::AnyScalar::bad_cast, O.as()); +} + +void test_ctor() +{ + testDiag("test_ctor()"); + pvd::AnyScalar A(10), + B(10.0), + C("foo"), + D(std::string("bar")); + + testEqual(A.type(), pvd::pvInt); + testEqual(B.type(), pvd::pvDouble); + testEqual(C.type(), pvd::pvString); + testEqual(D.type(), pvd::pvString); + + testEqual(A.ref(), 10); + testEqual(B.ref(), 10); + testEqual(C.ref(), "foo"); + testEqual(D.ref(), "bar"); +} + +void test_basic() +{ + testDiag("test_basic()"); + pvd::AnyScalar I(42); + + testOk1(!I.empty()); + testOk1(!!I); + + testEqual(I.type(), pvd::pvInt); + testEqual(I.ref(), 42); + testEqual(I.as(), 42); + testEqual(I.as(), 42.0); + testEqual(I.as(), "42"); + + testThrows(pvd::AnyScalar::bad_cast, I.ref()); + + { + std::ostringstream strm; + strm<() = 43; + + testEqual(I.ref(), 43); + testEqual(I.as(), 43); + testEqual(I.as(), 43.0); + + I = pvd::AnyScalar("hello"); + + testEqual(I.type(), pvd::pvString); + testEqual(I.ref(), "hello"); + testEqual(I.as(), "hello"); + + testThrows(pvd::AnyScalar::bad_cast, I.ref()); + + { + pvd::AnyScalar O(I); + testOk1(!I.empty()); + testOk1(!O.empty()); + + testEqual(I.ref(), "hello"); + testEqual(O.ref(), "hello"); + } + + { + pvd::AnyScalar O; + I.swap(O); + testOk1(I.empty()); + testOk1(!O.empty()); + + testThrows(pvd::AnyScalar::bad_cast, I.ref()); + testEqual(O.ref(), "hello"); + + I.swap(O); + } +} + +void test_swap() +{ + testDiag("test_swap()"); + + // pvd::AnyScalar::swap() has 3 cases each for LHS and RHS + // nil, string, and non-string + // So we have 9 cases to test + + { + pvd::AnyScalar A, B; + A.swap(B); + testOk1(A.empty()); + testOk1(B.empty()); + } + { + pvd::AnyScalar A, B("hello"); + A.swap(B); + testEqual(A.ref(), "hello"); + testOk1(B.empty()); + } + { + pvd::AnyScalar A, B(40); + A.swap(B); + testEqual(A.ref(), 40); + testOk1(B.empty()); + } + + { + pvd::AnyScalar A("world"), B; + A.swap(B); + testOk1(A.empty()); + testEqual(B.ref(), "world"); + } + { + pvd::AnyScalar A("world"), B("hello"); + A.swap(B); + testEqual(A.ref(), "hello"); + testEqual(B.ref(), "world"); + } + { + pvd::AnyScalar A("world"), B(40); + A.swap(B); + testEqual(A.ref(), 40); + testEqual(B.ref(), "world"); + } + + { + pvd::AnyScalar A(39), B; + A.swap(B); + testOk1(A.empty()); + testEqual(B.ref(), 39); + } + { + pvd::AnyScalar A(39), B("hello"); + A.swap(B); + testEqual(A.ref(), "hello"); + testEqual(B.ref(), 39); + } + { + pvd::AnyScalar A(39), B(40); + A.swap(B); + testEqual(A.ref(), 40); + testEqual(B.ref(), 39); + } +} + +void test_move() +{ + testDiag("test_move()"); +#if __cplusplus>=201103L + { + pvd::AnyScalar x, y(std::move(x)); + testOk1(x.empty()); + testOk1(y.empty()); + } + { + pvd::AnyScalar x(5), y(std::move(x)); + testOk1(x.empty()); + testEqual(y.ref(), 5); + } + { + pvd::AnyScalar x("hello"), y(std::move(x)); + testOk1(x.empty()); + testEqual(y.ref(), "hello"); + } + + { + pvd::AnyScalar x, y; + y = std::move(x); + testOk1(x.empty()); + testOk1(y.empty()); + } + { + pvd::AnyScalar x, y(5); + y = std::move(x); + testOk1(x.empty()); + testOk1(y.empty()); + } + { + pvd::AnyScalar x, y("test"); + y = std::move(x); + testOk1(x.empty()); + testOk1(y.empty()); + } +#else + testSkip(12, "No c++11"); +#endif +} + +} // namespace + +MAIN(testanyscalar) +{ + testPlan(66); + try { + test_empty(); + test_ctor(); + test_basic(); + test_swap(); + test_move(); + }catch(std::exception& e){ + testAbort("Unexpected exception: %s", e.what()); + } + return testDone(); +}