diff --git a/src/lib/variant.hpp b/src/lib/variant.hpp index 1581e0b43..cffb94804 100644 --- a/src/lib/variant.hpp +++ b/src/lib/variant.hpp @@ -34,6 +34,14 @@ ** Type mismatch is checked at runtime. As a fallback, we provide a visitor ** scheme for generic access. ** + ** The design restrictions were chosen deliberately, since a variant type might + ** promote "probe and switch on type" style programming, which is known to be fragile. + ** Likewise, we do not want to support mutations of the variant type at runtime. Basically, + ** using a variant record is recommended only if either the receiving context has structural + ** knowledge about the type to expect, or when a visitor implementation can supply a sensible + ** handling for \em all the possible types. As an alternative, you might consider the + ** lib::PolymorphicValue to hold types implementing a common interface. + ** ** \par implementation notes ** We use a similar "double capsule" implementation technique as for lib::OpaqueHolder. ** In fact, Variant is almost identical to the latter, just omitting unnecessary flexibility. @@ -46,6 +54,11 @@ ** denote the smallest disposable storage size for the given platform after alignment, ** typically the size of a size_t). ** + ** To support copying and assignment of variant instances, but limit these operations + ** to variants holding the same type, we use a virtual assignment function. In case the + ** concrete type does not support assignment or copy construction, the respective access + ** function is replaced by an implementation raising a runtime error. + ** ** @see Veriant_test ** @see lib::diff::GenNode ** @@ -59,13 +72,8 @@ #include "lib/meta/typelist.hpp" #include "lib/meta/typelist-util.hpp" #include "lib/meta/generator.hpp" -//#include "lib/util.hpp" #include -#include -//#include -//#include -//#include namespace lib { @@ -78,9 +86,6 @@ namespace lib { namespace error = lumiera::error; - template - class Variant; - namespace { // implementation helpers @@ -89,8 +94,6 @@ namespace lib { using std::is_copy_constructible; using std::is_copy_assignable; using std::remove_reference; - using std::remove_const; - using std::is_same; using std::enable_if; using meta::NullType; using meta::Node; @@ -122,29 +125,6 @@ namespace lib { }; - template - struct Bare - { - using Type = typename remove_const< - typename remove_reference::type>::type; - }; - - template - struct is_Variant - { - static constexpr bool value = false; - }; - - template - struct is_Variant> - { - static constexpr bool value = true; - }; - - template - struct use_if_is_Variant - : enable_if::Type>::value, V> - { }; @@ -195,7 +175,7 @@ namespace lib { template class MoveSupport - : NoCopyMoveSupport + : public NoCopyMoveSupport { virtual void copyInto (void*) const override @@ -269,25 +249,22 @@ namespace lib { : enable_if< is_move_constructible::value && !is_copy_constructible::value && !can_use_assignment::value - ,X > { }; - + template struct use_if_supports_cloning : enable_if< is_move_constructible::value && is_copy_constructible::value && !can_use_assignment::value - ,X > { }; - + template struct use_if_supports_copy_and_assignment : enable_if< is_move_constructible::value && is_copy_constructible::value && can_use_assignment::value - ,X > { }; @@ -339,6 +316,7 @@ namespace lib { }//(End) implementation helpers + /** * Typesafe union record. * A Variant element may carry an embedded value of any of the predefined types. @@ -350,6 +328,7 @@ namespace lib { * any access to the variant's value requires knowledge of the type * in question, but type mismatch will provoke an exception at runtime. * Generic access is possible using a visitor. + * @warning not threadsafe */ template class Variant @@ -547,6 +526,7 @@ namespace lib { using RawType = typename remove_reference::type; static_assert (meta::isInList(), "Type error: the given variant could never hold the required type"); + static_assert (can_use_assignment::value, "target type does not support assignment"); buff() = forward(x); return *this; @@ -605,7 +585,7 @@ namespace lib { /* == diagnostic helper == */ - + #ifdef LIB_FORMAT_UTIL_H namespace lib { @@ -624,7 +604,7 @@ namespace lib { (util::tyStr()+"|").c_str() ); } -} // namespace lib +}// namespace lib #endif #endif /*LIB_VARIANT_H*/ diff --git a/tests/15library.tests b/tests/15library.tests index 4f35634a2..f3269c90e 100644 --- a/tests/15library.tests +++ b/tests/15library.tests @@ -99,7 +99,7 @@ END TEST "formatting/diagnostics helpers" FormatHelper_test < out: ^hey Joe! out: ^he says: hey Joe! @@ -486,6 +486,15 @@ return: 0 END +TEST "typesafe Variant record" Variant_test < 'Woof-Woof!' out-lit: consuming honk -> 'Honk-Honk!' diff --git a/tests/library/variant-test.cpp b/tests/library/variant-test.cpp index 4b63e349a..d4f6c6a91 100644 --- a/tests/library/variant-test.cpp +++ b/tests/library/variant-test.cpp @@ -30,12 +30,8 @@ #include "lib/util.hpp" -//#include -#include #include -//#include -//using boost::lexical_cast; -//#include +#include @@ -53,16 +49,15 @@ namespace test{ using std::endl; using error::LUMIERA_ERROR_WRONG_TYPE; + using error::LUMIERA_ERROR_LOGIC; - namespace { // test fixture... - - }//(End) test fixture - + // Test fixture... typedef Variant> TestVariant; + /****************************************************************************//** * @test cover the essential properties of Lumiera's lightweight variant record. * - this typesafe union can never be constructed empty @@ -228,6 +223,12 @@ namespace test{ VERIFY_ERROR (WRONG_TYPE, v1 = v2 ); VERIFY_ERROR (WRONG_TYPE, v1 = 22L ); VERIFY_ERROR (WRONG_TYPE, v2 = string("2")); + + TestVariant v4 = Time(); + TestVariant v44 = Time(0,4,4,4); // OK: copy initialisation + VERIFY_ERROR (LOGIC, v4 = v44); // Runtime Error: not assignable + + // v44 = Time(4,4); // does not compile: Time is not assignable } };