/* AccessCasted(Test) - verify helper to cast or convert as appropriate Copyright (C) 2008, Hermann Vosseler   **Lumiera** is free software; you can redistribute it and/or modify it   under the terms of the GNU General Public License as published by the   Free Software Foundation; either version 2 of the License, or (at your   option) any later version. See the file COPYING for further details. * *****************************************************************/ /** @file access-casted-test.cpp ** unit test \ref AccessCasted_test */ #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" #include "lib/access-casted.hpp" #include "lib/format-cout.hpp" #include "lib/util.hpp" #include #include using std::move; using std::string; using std::ostream; using util::isSameObject; using LERR_(BOTTOM_VALUE); using LERR_(WRONG_TYPE); namespace util { namespace test { namespace { // Test fixture... struct B {}; struct D : B {}; struct E : D { virtual ~E() {}; }; struct X { char x = 'x'; }; struct F : X , E { }; ostream& operator<< (ostream& s, const B& b) { return s << "B{} adr="<<&b; } ostream& operator<< (ostream& s, const D& d) { return s << "D{} adr="<<&d; } ostream& operator<< (ostream& s, const E& e) { return s << "E{} adr="<<&e; } ostream& operator<< (ostream& s, const F& f) { return s << "F{} adr="<<&f; } }//(End)Test fixture /*************************************************************************************************//** * @test verify a helper template for accessing values either through conversion or (dynamic) downcast. * Typically, this helper is used in value holder containers or variant-like data structures, * where the actual type is given at instantiation time of the template and possibly erased. * * @warning we can not really cover the negative cases, which ought to be rejected by the compiler. * These cases have been verified one by one, and are retained commented out. You ought * to re-check these cases manually when using a new compiler. */ class AccessCasted_test : public Test { virtual void run (Arg) { D d; D* pD =&d; B* pB =pD; D& rD = *pD; B& rB = *pB; D*& rpD = pD; B*& rpB = pB; E e; E& rE = e; F f; E& rEF = f; E* pEF = &f; X* pXF = &f; F* pF = &f; cout << "can_downcast = " << can_downcast::value < = " << can_downcast::value < = " << can_downcast::value < = " << can_downcast::value < = " << can_downcast::value < = " << can_downcast::value < = " << can_downcast::value < = " << can_downcast::value < = " << can_downcast::value < = " << has_RTTI::value < = " << has_RTTI::value < = " << has_RTTI::value < = " << std::is_convertible::value < = " << std::is_convertible::value < = " << can_use_dynamic_downcast::value < = " << can_use_conversion::value < = " << can_use_dynamic_downcast::value < = " << can_use_conversion::value < = " << can_use_dynamic_downcast::value < = " << can_use_conversion::value < = " << can_use_conversion::value < = " << can_use_dynamic_downcast::value < = " << can_use_conversion::value < = " << can_use_dynamic_downcast::value <" << AccessCasted::access(d) <" << AccessCasted::access(rD) <::access(move(dd1)); // does not compile since it would be dangerous; we can't take a l-value ref // AccessCasted::access(rD); // from a r-value (move) reference and we can't move a l-value ref // AccessCasted::access(d); // cout << "=== build a value object ==="<" << AccessCasted::access(d) <" << AccessCasted::access(rD) <" << AccessCasted::access(move(dd1)) <" << AccessCasted::access(d) <" << AccessCasted::access(rD) <::access(move(dd1)); // should not take value moved by r-value-ref as pointer, otherwise the moved object would be lost cout << "=== dereference a pointer ==="<" << AccessCasted::access(pD) <" << AccessCasted::access(pD) <" << AccessCasted::access(move(pdd1)) <::access(pNull)); // run-time NULL check // AccessCasted::access(pD); // should not move away a value accessed through a pointer, there might be other users cout << "=== const correctness ==="<" << AccessCasted::access(d) <" << AccessCasted::access(rD) <" << AccessCasted::access(d) <" << AccessCasted::access(rD) <" << AccessCasted::access(d) <" << AccessCasted::access(rD) <" << AccessCasted::access(pD) <" << AccessCasted::access(pD) <" << AccessCasted::access(cD) <" << AccessCasted::access(rcD) <" << AccessCasted::access(cD) <" << AccessCasted::access(rcD) <" << AccessCasted::access(cD) <" << AccessCasted::access(rcD) <" << AccessCasted::access(pcD) <" << AccessCasted::access(pcD) <" << AccessCasted::access(rcD) <" << AccessCasted::access(move(cD1)) <::access(rcD); // normal ref from const ref is not const correct // AccessCasted::access(rcD); // likewise, regular pointer from const ref prohibited // AccessCasted::access(pcD); // likewise, regular ref from pointer-to-const // AccessCasted::access(pcD); // and regular pointer from pointer-to-const // AccessCasted::access(rcD); // ruled out already because moving a reference is invalid // AccessCasted::access(pcD); // ruled out already because moving a dereferenced pointer is invalid // AccessCasted::access(move(cD)); // ruled out already because taking reference from moved value is invalid // AccessCasted::access(move(cD)); // and same for taking pointer from a moved value. cout << "=== work cases: actual conversions ==="<" << AccessCasted::access(rB) <" << AccessCasted::access(rD) <" << AccessCasted::access(pB) <" << AccessCasted::access(pD) <" << AccessCasted::access(rD) <" << AccessCasted::access(pD) <" << AccessCasted::access(rpB) <" << AccessCasted::access(rpD) <" << AccessCasted::access(rD) <" << AccessCasted::access(pD) <" << AccessCasted::access(rcD) <" << AccessCasted::access(pcD) <::access(rpD); // ruled out, since it would allow to sneak-in a non-D object into the D* // AccessCasted::access(rB); // any down-casts are ruled out, // AccessCasted::access(pB); // since neither B nor D has RTTI // AccessCasted::access(pB); // // AccessCasted::access(rB); // // AccessCasted::access(rD); // we need RTTI on both ends to perform a safe dynamic downcast. // AccessCasted::access((B*)pD); // dangerous, since we have no way to know for sure it's indeed a D object // AccessCasted::access(pDE); // same here, since E has RTTI but D hasn't, we have no way to find out the real type VERIFY_ERROR (WRONG_TYPE, AccessCasted::access(rE)); // allowed by typing, but fails at runtime since it isn't an F-object cout << "Access(E(F)& as F&) --->" << AccessCasted::access(rEF) <" << AccessCasted::access(pEF) <" << AccessCasted::access(pEF) <" << AccessCasted::access(pEF) <" << AccessCasted::access(pF) <" << AccessCasted::access(pXF) <" << AccessCasted::access(pF) <" << AccessCasted::access(pF) <::access(pEF); // cross-cast not supported (to complicated to implement) // AccessCasted::access(pXF); // downcast not possible, since X does not provide RTTI int i = 2; float fp = 3.1415; cout << "Access(int as double) --->" << AccessCasted::access(i) <" << AccessCasted::access(fp) <::access(i); // would undermine the type system, thus ruled out // AccessCasted::access(i); // allowed, but warning: returning reference to temporary (and the warning is justified) CHECK (isSameObject (d, AccessCasted::access(d))); CHECK (isSameObject (rD, AccessCasted::access(pD))); CHECK (isSameObject (d, AccessCasted::access(pD))); CHECK (!isSameObject (d, AccessCasted::access(rD))); CHECK (isSameObject (f, AccessCasted::access(rEF))); CHECK (!isSameObject (f, AccessCasted::access(pF))); // note: address adjustment due to layout of mixin object X } }; /** Register this test class... */ LAUNCHER (AccessCasted_test, "unit common"); }} // namespace lib::meta::test