Skip to content
Snippets Groups Projects
Commit e22c67a6 authored by David Thompson's avatar David Thompson
Browse files

Add `RuntimeTypeContainer` to smtk::common.

This container provides methods on top of `TypeContainer`
for holding multiple objects that share a common base type
by indexing them at a "declared type" hash.
parent 8eb3c08a
No related branches found
No related tags found
No related merge requests found
Pipeline #368431 failed
Changes to common infrastructure
--------------------------------
TypeContainer changes
~~~~~~~~~~~~~~~~~~~~~
A few minor changes were made to :smtk:`smtk::common::TypeContainer`:
+ Methods and variables that were previously private are now protected so that
this class can be subclassed.
+ The wrapper class used to store objects in the type container now provides a
string token holding the type-name of the inserted type.
This is used by the new :smtk:`smtk::common::RuntimeTypeContainer` subclass described below.
+ The ``insert_or_assign()`` method has been renamed ``insertOrAssign()``
to be consistent with the rest of SMTK.
The original name is deprecated and will be removed in a future version of SMTK.
New RuntimeTypeContainer
~~~~~~~~~~~~~~~~~~~~~~~~
:smtk:`smtk::common::RuntimeTypeContainer` is a new subclass of TypeContainer.
The base TypeContainer class can only hold a single object of a given type.
When applications handle many objects that share a common base type (i.e., whose
complete type is unknown since only a pointer to a base type is held),
there was no way to insert these distinct objects into a TypeContainer even if
their complete types were distinct.
To resolve this issue, the RuntimeTypeContainer class allows you to insert
objects by their base type but use a "declared type-name" as the key.
As long as these declared type-names are unique, multiple objects sharing the
base type can be held by the container simultaneously.
See the class and its test for detailed documentation.
......@@ -17,3 +17,18 @@ an instance of that object.
An example that demonstrates the prinicples and API of this pattern
can be found at `smtk/comon/testing/cxx/UnitTestTypeContainer.cxx`.
Run-time Type Container
=======================
:smtk:`smtk::common::RuntimeTypeContainer` is a subclass of TypeContainer.
The base TypeContainer class can only hold a single object of a given type.
When applications handle many objects that share a common base type (i.e., whose
complete type is unknown since only a pointer to a base type is held),
there was no way to insert these distinct objects into a TypeContainer even if
their complete types were distinct.
To resolve this issue, the RuntimeTypeContainer class allows you to insert
objects by their base type but use a "declared type-name" as the key.
As long as these declared type-names are unique, multiple objects sharing the
base type can be held by the container simultaneously.
......@@ -13,6 +13,7 @@ set(commonSrcs
json/jsonVersionNumber.cxx
Managers.cxx
Paths.cxx
RuntimeTypeContainer.cxx
Status.cxx
StringUtil.cxx
TimeZone.cxx
......@@ -51,6 +52,7 @@ set(commonHeaders
Paths.h
Processing.h
RangeDetector.h
RuntimeTypeContainer.h
Singleton.h
Status.h
StringUtil.h
......
//=========================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//=========================================================================
#include "smtk/common/RuntimeTypeContainer.h"
namespace smtk
{
namespace common
{
RuntimeTypeContainer::~RuntimeTypeContainer() = default;
RuntimeTypeContainer::RuntimeTypeContainer(const RuntimeTypeContainer& other)
{
*this = other;
}
RuntimeTypeContainer& RuntimeTypeContainer::operator=(const RuntimeTypeContainer& other)
{
for (const auto& entry : other.m_container)
{
m_container.emplace(std::make_pair(entry.first, entry.second->clone()));
}
m_runtimeObjects = other.m_runtimeObjects;
return *this;
}
std::unordered_set<smtk::string::Token> RuntimeTypeContainer::runtimeBaseTypes()
{
std::unordered_set<smtk::string::Token> result;
for (const auto& entry : m_runtimeObjects)
{
result.insert(entry.first);
}
return result;
}
std::unordered_set<smtk::string::Token> RuntimeTypeContainer::runtimeTypeNames(smtk::string::Token baseType)
{
auto it = m_runtimeObjects.find(baseType);
if (it == m_runtimeObjects.end())
{
std::unordered_set<smtk::string::Token> empty;
return empty;
}
return it->second;
}
} // namespace common
} // namespace smtk
//=========================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//=========================================================================
#ifndef smtk_common_RuntimeTypeContainer_h
#define smtk_common_RuntimeTypeContainer_h
#include "smtk/common/TypeContainer.h"
namespace smtk
{
namespace common
{
/**\brief A container that can hold objects by their exact compile-time
* type as well as a commonly-inherited, run-time type.
*
* This class extends TypeContainer with a runtime API that
* stores objects of a common type under user-provided "type names"
* chosen at run time. (These "type names" do not need to name
* actual types, but they may.)
*
* ## Insertion and retrieval
*
* For example, consider a TypeContainer used to hold metadata
* objects. Compile-time objects can be inserted during the build
* but users may need to add "programmable" metadata objects at
* run-time. The application can provide a base RuntimeMetadata
* class with virtual methods and then allow users to "name"
* instances of this base class as if each represented its own type.
*
* ```cpp
* struct MetadataA { const char* name = "Foo"; };
* struct MetadataB { const char* name = "Bar"; };
* class RuntimeMetadata
* {
* public:
* RuntimeMetadata(const std::string& name) : m_name(name) { }
* virtual std::string name() const { return m_name; }
* private:
* std::string m_name;
* };
*
* class Baz : public RuntimeMetadata
* {
* public:
* Baz() : RuntimeMetadata("Baz") { };
* };
*
* RuntimeTypeContainer allMetadata(MetadataA(), MetadataB());
* …
* allMetadata.emplaceRuntime("Baz", RuntimeMetadata("Baz"));
* ```
*
* In this example, there are now two "compile-time" entries
* in `allMetadata` plus one "run-time" entry. You can fetch
* "Foo" and "Bar" by knowing their type at compile time:
* ```cpp
* auto& foo = allMetadata.get<Foo>();
* auto& bar = allMetadata.get<Bar>();
* ```
* You can fetch "Baz" by knowing *either* its type or that it
* inherits RuntimeMetadata:
* ```cpp
* auto bazDirect = allMetadata.get<Baz>();
* auto bazByBase = allMetadata.getRuntime<RuntimeMetadata>("Baz");
* ```
*
* Importantly, (1) you can store multiple "run-time" objects sharing the
* same base class in the type container as long as their declared types
* are unique and (2) the declared type does not need to match any actual
* type. However, (3) if fetching by a run-time object's base type, the
* template parameter must exactly match the type used to insert the
* object into the TypeContainer.
*
* For example:
* ```cpp
* class Xyzzy : public Baz { };
* allMetadata.emplaceRuntime<RuntimeMetadata>("Xyzzy", Xyzzy());
* auto dataBad = allMetadata.getRuntime<Baz>("Xyzzy"); // Will NOT work.
* auto dataGood = allMetadata.getRuntime<RuntimeMetadata>("Xyzzy"); // Correct.
* ```
*
* ## Introspection of runtime type-data
*
* It is also important for consumers of a RuntimeTypeContainer to
* be able to fetch the list of run-time objects and their base types.
* To this end, you can access a map from run-time storage types to
* the user-provided "type names."
*
* ```cpp
* // Fetch a set of string-tokens naming the base types of runtime objects:
* auto runtimeBases = allMetadata.runtimeBaseTypes();
*
* // Return the type-names of objects that "inherit" a given runtime base type:
* auto runtimeObjectTypes = allMetadata.runtimeTypeNames("RuntimeMetadata");
*
* ```
*/
class SMTKCORE_EXPORT RuntimeTypeContainer : public TypeContainer
{
public:
/// Construct an empty RuntimeTypeContainer.
RuntimeTypeContainer() = default;
/// Construct a RuntimeTypeContainer whose contents are copied from an existing
/// RuntimeTypeContainer.
RuntimeTypeContainer(const RuntimeTypeContainer&);
/// Move the contents of one TypeContainer into a new TypeContainer.
RuntimeTypeContainer(RuntimeTypeContainer&&) = default;
/// Copy the contents of an existing TypeContainer into this one.
RuntimeTypeContainer& operator=(const RuntimeTypeContainer&);
/// Move the contents of an existing TypeContainer into this one.
RuntimeTypeContainer& operator=(RuntimeTypeContainer&&) = default;
/// Construct a RuntimeTypeContainer instance from any number of elements. Elements
/// are added in the order they appear in the constructor, so subsequent values
/// for the same type will be ignored.
template<
typename Arg,
typename... Args,
typename std::enable_if<!std::is_base_of<TypeContainer, Arg>::value, int>::type = 0>
RuntimeTypeContainer(const Arg& arg, const Args&... args)
{
insertAll(arg, args...);
}
virtual ~RuntimeTypeContainer();
/// Check if a Type is present in the TypeContainer.
bool containsRuntime(smtk::string::Token declaredType) const
{
return (m_container.find(declaredType.id()) != m_container.end());
}
/// Insert a runtime \a RuntimeType instance into the TypeContainer.
/// Note that if the type already exists in the container, the insertion will fail.
template<typename RuntimeType, typename ActualType>
bool insertRuntime(smtk::string::Token declaredType, const ActualType& value)
{
static_assert(std::is_convertible<ActualType*, RuntimeType*>::value,
"Inserted object must inherit the requested base Type.");
bool didInsert = m_container
.emplace(std::make_pair(
declaredType.id(),
#ifdef SMTK_HAVE_CXX_14
std::make_unique<WrapperFor<RuntimeType>>(std::make_unique<ActualType>(value))
#else
std::unique_ptr<Wrapper>(new WrapperFor<RuntimeType>(std::unique_ptr<ActualType>(new ActualType((value)))))
#endif
))
.second;
if (didInsert)
{
m_runtimeObjects[smtk::common::typeName<RuntimeType>()].insert(declaredType);
}
return didInsert;
}
/// Insert a \a Type instance into the RuntimeTypeContainer
/// if it does not exist already or replace it if it does.
template<typename RuntimeType, typename ActualType>
bool insertOrAssignRuntime(smtk::string::Token declaredType, const ActualType& value)
{
if (this->containsRuntime(declaredType))
{
this->eraseRuntime(declaredType);
}
return this->insertRuntime<RuntimeType>(declaredType.id(), value);
}
/// Emplace a RuntimeType instance into the TypeContainer.
template<typename RuntimeType, typename... Args>
bool emplaceRuntime(smtk::string::Token declaredType, Args&&... args)
{
bool didInsert = m_container
.emplace(std::make_pair(
declaredType.id(),
#ifdef SMTK_HAVE_CXX_14
std::make_unique<WrapperFor<RuntimeType>>(std::make_unique<RuntimeType>(std::forward<Args>(args)...))
#else
std::unique_ptr<Wrapper>(
new WrapperFor<RuntimeType>(std::unique_ptr<RuntimeType>(new RuntimeType(std::forward<Args>(args)...))))
#endif
))
.second;
if (didInsert)
{
m_runtimeObjects[smtk::common::typeName<RuntimeType>()].insert(declaredType);
}
return didInsert;
}
/// Access a Type instance, and throw if it is not in the TypeContainer.
template<typename RuntimeType>
const RuntimeType& getRuntime(smtk::string::Token declaredType) const
{
auto search = m_container.find(declaredType.id());
if (search == m_container.end())
{
throw BadTypeError(declaredType.data());
}
// TODO: Check that \a RuntimeType was the type used to insert \a declaredType.
// Throw BadTypeError(smtk::common::typeName<RuntimeType>()) if not.
return *(static_cast<WrapperFor<RuntimeType>*>(search->second.get()))->value;
}
/// For default-constructible types, access a RuntimeType instance, creating one if it
/// is not in the RuntimeTypeContainer.
template<typename RuntimeType>
typename std::enable_if<std::is_default_constructible<RuntimeType>::value, RuntimeType&>::type getRuntime(smtk::string::Token declaredType) noexcept
{
auto search = m_container.find(declaredType.id());
if (search == m_container.end())
{
search = m_container
.emplace(std::make_pair(
declaredType.id(),
#ifdef SMTK_HAVE_CXX_14
std::make_unique<WrapperFor<RuntimeType>>(std::make_unique<RuntimeType>())
#else
std::unique_ptr<Wrapper>(new WrapperFor<RuntimeType>(std::unique_ptr<RuntimeType>(new RuntimeType)))
#endif
))
.first;
}
// TODO: Check that \a RuntimeType was the type used to insert \a declaredType.
// Throw BadTypeError(smtk::common::typeName<RuntimeType>()) if not.
return *(static_cast<WrapperFor<RuntimeType>*>(search->second.get()))->value;
}
/// For non-default-constructible types, access a Type instance; throw if it is
/// not in the TypeContainer.
template<typename RuntimeType>
typename std::enable_if<!std::is_default_constructible<RuntimeType>::value, RuntimeType&>::type getRuntime(smtk::string::Token declaredType)
{
auto search = m_container.find(declaredType.id());
if (search == m_container.end())
{
throw BadTypeError(declaredType.data());
}
// TODO: Check that \a RuntimeType was the type used to insert \a declaredType.
// Throw BadTypeError(smtk::common::typeName<RuntimeType>()) if not.
return *(static_cast<WrapperFor<RuntimeType>*>(search->second.get()))->value;
}
/// Remove a specific type of object from the container.
///
/// This is overridden from the base class to ensure m_runtimeObjects is maintained.
template<typename Type>
bool erase()
{
auto it = m_container.find(this->keyId<Type>());
if (it != m_container.end())
{
// Check whether the object was indexed in m_runtimeObjects
// and remove it if needed.
auto baseTypeIt = m_runtimeObjects.find(it->second->objectType());
if (baseTypeIt != m_runtimeObjects.end())
{
// Note that the only way erase<T>() can work is if the declared
// type matches the declared type-name in it->first:
baseTypeIt->second.erase(smtk::string::Token::fromHash(it->first));
}
// Now erase the container entry.
m_container.erase(it);
return true;
}
return false;
}
/// Remove a specific type of object from the container.
bool eraseRuntime(smtk::string::Token declaredType)
{
auto it = m_container.find(declaredType.id());
if (it != m_container.end())
{
// Check whether the object was indexed in m_runtimeObjects
// and remove it if needed.
auto baseTypeIt = m_runtimeObjects.find(it->second->objectType());
if (baseTypeIt != m_runtimeObjects.end())
{
baseTypeIt->second.erase(declaredType);
}
// Now erase the container entry.
m_container.erase(it);
return true;
}
return false;
}
/// Erase all objects held by the container.
void clear() noexcept
{
this->TypeContainer::clear();
m_runtimeObjects.clear();
}
/// Report the base classes used to insert runtime objects.
std::unordered_set<smtk::string::Token> runtimeBaseTypes();
/// Report the declared type-names of objects inserted using the given \a base type
std::unordered_set<smtk::string::Token> runtimeTypeNames(smtk::string::Token baseType);
/// Report the declared type-names of objects inserted using the given \a base type
template<typename RuntimeType>
std::unordered_set<smtk::string::Token> runtimeTypeNames()
{
return this->runtimeTypeNames(smtk::common::typeName<RuntimeType>());
}
protected:
/// Map from RuntimeType type-names (common base classes used to insert values) to declared types.
std::unordered_map<smtk::string::Token, std::unordered_set<smtk::string::Token>> m_runtimeObjects;
};
} // namespace common
} // namespace smtk
#endif
......@@ -44,6 +44,7 @@ protected:
{
virtual ~Wrapper() = default;
virtual std::unique_ptr<Wrapper> clone() const = 0;
virtual smtk::string::Token objectType() const = 0;
};
template<typename Type>
......@@ -53,6 +54,7 @@ protected:
WrapperFor(Args&&... v)
: value(std::forward<Args>(v)...)
{
m_objectType = smtk::common::typeName<Type>();
}
std::unique_ptr<Wrapper> clone() const override
......@@ -63,8 +65,13 @@ protected:
return std::unique_ptr<Wrapper>(new WrapperFor<Type>(new Type(*value)));
#endif
}
smtk::string::Token objectType() const override
{
return m_objectType;
}
std::unique_ptr<Type> value;
smtk::string::Token m_objectType;
};
public:
......
......@@ -42,6 +42,7 @@ set(unit_tests
UnitTestInfixExpressionGrammarImpl.cxx
UnitTestLinks.cxx
UnitTestObservers.cxx
UnitTestRuntimeTypeContainer.cxx
UnitTestThreadPool.cxx
UnitTestTypeContainer.cxx
UnitTestTypeMap.cxx
......
//=========================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//=========================================================================
#include "smtk/common/CompilerInformation.h" // for expectedKeys
#include "smtk/common/RuntimeTypeContainer.h"
#include "smtk/common/testing/cxx/helpers.h"
#include <cmath>
namespace
{
const double float_epsilon = 1.e-6;
struct Foo
{
Foo(int i)
: value(i)
{
}
int value;
};
struct Bar
{
Bar(int i)
: value(i)
{
}
Bar(const Bar& other) = default;
Bar(Bar&& other) = default;
int value;
};
struct Base
{
Base() = default;
Base(const std::string& suffix) : m_data("_" + suffix) { }
Base(const Base&) = default;
Base(Base&&) = default;
Base& operator = (const Base&) = default;
bool operator == (const Base& other) { return m_data == other.m_data; }
std::string m_data;
virtual std::string name() const { return "Base" + m_data; }
};
struct Derived : public Base
{
Derived() = default;
Derived(const std::string& suffix) : Base("d1_" + suffix) { }
Derived(const Derived&) = default;
Derived(Derived&&) = default;
Derived& operator = (const Derived&) = default;
std::string name() const override { return "Derived" + m_data; }
};
struct Derived2 : public Derived
{
Derived2() = default;
Derived2(const std::string& suffix) : Derived("d2_" + suffix) { }
Derived2(const Derived2&) = default;
Derived2(Derived2&&) = default;
Derived2& operator = (const Derived2&) = default;
std::string name() const override { return "Derived2" + m_data; }
};
} // namespace
int UnitTestRuntimeTypeContainer(int /*unused*/, char** const /*unused*/)
{
using namespace smtk::string::literals;
smtk::common::RuntimeTypeContainer typeContainer;
// I. First, test that all the base-class TypeContainer stuff works as usual:
test(typeContainer.empty(), "New instance shoud contain no values.");
test(!typeContainer.contains<int>(), "New instance should contain no values of integer type.");
typeContainer.get<int>() = 3;
test(typeContainer.size() == 1, "Assigned value should increment the container size.");
test(typeContainer.get<int>() == 3, "Assigned value should be retrievable.");
typeContainer.clear();
test(typeContainer.empty(), "Cleared instance shoud contain no values.");
test(
!typeContainer.contains<int>(), "Cleared instance should contain no values of integer type.");
typeContainer.insert<float>(2.3f);
test(
fabs(typeContainer.get<float>() - 2.3f) < float_epsilon,
"Assigned value should be retrievable.");
try
{
typeContainer.get<Foo>() = Foo(3);
test(false, "Access to a type with no default constructor should throw an error.");
}
catch (const std::out_of_range&)
{
}
typeContainer.emplace<Foo>(3);
test(typeContainer.get<Foo>().value == 3, "Assigned value should be retrievable.");
typeContainer.insert<Bar>(Bar(2));
test(typeContainer.get<Bar>().value == 2, "Assigned value should be retrievable.");
// II. Now test RuntimeTypeContainer-specific methods.
typeContainer.insertRuntime<Base>("NotReallyBase", Derived("nr"));
typeContainer.insertRuntime<Base>("FarFromBase", Derived2("ff"));
typeContainer.emplaceRuntime<Base>("(anonymous namespace)::Base", "bb");
auto base0Name = typeContainer.getRuntime<Base>("(anonymous namespace)::Base").name();
auto base1Name = typeContainer.getRuntime<Base>("NotReallyBase").name();
auto base2Name = typeContainer.getRuntime<Base>("FarFromBase").name();
// clang-format off
std::cout
<< "Runtime insertion via base class produced:\n"
<< " 1. " << base0Name << "\n"
<< " 2. " << base1Name << "\n"
<< " 3. " << base2Name << "\n"
;
// clang-format on
test(base0Name == "Base_bb", "Did not properly emplace base with non-default ctor.");
test(base1Name == "Derived_d1_nr", "Did not properly insert derived with non-default ctor.");
test(base2Name == "Derived2_d1_d2_ff", "Did not properly insert derived2 with non-default ctor.");
// Test that we can fetch "special" runtime objects using the inherited API.
// (Special in the sense that their declared type-name is their actual type-name.)
base2Name = typeContainer.get<Base>().name();
std::cout << "Fetched via base get<>() API: " << base2Name << "\n";
test(base2Name == "Base_bb", "Failed to fetch specially-named base object with inherited API.");
// Test that insertOrAssignRuntime() works
Derived2 dummy;
typeContainer.insertOrAssignRuntime<Base>("(anonymous namespace)::Base", dummy);
base1Name = typeContainer.get<Base>().name();
std::cout << "Assigning to existing key (should overwrite) produced " << base1Name << "\n";
test(base1Name == "Derived2", "Failed to overwrite existing storage with new value.");
dummy = Derived2("dd");
typeContainer.insertOrAssignRuntime<Base>("NewlyInserted", dummy);
test(typeContainer.containsRuntime("NewlyInserted"), "Did not insert new value.");
base1Name = typeContainer.getRuntime<Base>("NewlyInserted").name();
std::cout << "Assigning to new key (should insert) produced " << base1Name << "\n";
test(base1Name == "Derived2_d1_d2_dd", "Failed to overwrite existing storage with new value.");
// III. Test copying of RuntimeTypeContainer objects.
smtk::common::RuntimeTypeContainer typeContainer2(typeContainer);
test(typeContainer2.get<Foo>().value == 3, "Copied container should behave like the original.");
test(typeContainer2.get<Bar>().value == 2, "Copied container should behave like the original.");
for (const auto& runtimeTypeName : typeContainer2.runtimeTypeNames(smtk::common::typeName<Base>()))
{
std::ostringstream msg;
msg << "Expected copied type container to have matching " << runtimeTypeName.data() << ".";
test(typeContainer.getRuntime<Base>(runtimeTypeName) ==
typeContainer2.getRuntime<Base>(runtimeTypeName), msg.str());
}
smtk::common::RuntimeTypeContainer
typeContainer3(typeContainer2.get<Foo>(), typeContainer2.get<Bar>());
test(
typeContainer3.get<Foo>().value == 3,
"Variadic constructed container should behave like the original.");
test(
typeContainer3.get<Bar>().value == 2,
"Variadic constructed container should behave like the original.");
// IV. Print out membership of out copied type container:
std::set<smtk::string::Token> expectedKeys{
{
"float"_token, "(anonymous namespace)::Foo"_token, "(anonymous namespace)::Bar"_token,
"(anonymous namespace)::Base", "NotReallyBase", "FarFromBase", "NewlyInserted"
}
};
std::unordered_set<smtk::string::Token> expectedRuntimeTypeNames{
{
"(anonymous namespace)::Base", "NotReallyBase", "FarFromBase", "NewlyInserted"
}
};
smtk::string::Token baseTypeName;
{
std::cout << "Type container now holds:\n";
for (const auto& token : typeContainer2.keys())
{
std::cout << " " << token.data() << " (" << std::hex << token.id() << std::dec << ")\n";
}
test(typeContainer2.keys() == expectedKeys, "Container keys were improperly reported.");
std::cout << "Runtime base-class types:\n";
for (const auto& token : typeContainer2.runtimeBaseTypes())
{
baseTypeName = token;
std::cout << " " << token.data() << "\n";
for (const auto& token2 : typeContainer2.runtimeTypeNames(token))
{
std::cout << " " << token2.data() << "\n";
}
}
test(typeContainer2.runtimeTypeNames(baseTypeName) == expectedRuntimeTypeNames,
"Run-time type information was not kept up-to-date.");
}
test(typeContainer2.erase<Base>(), "Did not erase eponymous Base object.");
expectedRuntimeTypeNames.erase("(anonymous namespace)::Base");
test(typeContainer2.runtimeTypeNames(baseTypeName) == expectedRuntimeTypeNames,
"Run-time type information was not kept up-to-date during templated erasure.");
test(typeContainer2.eraseRuntime("NewlyInserted"), "Did not erase NewlyInserted object.");
expectedRuntimeTypeNames.erase("NewlyInserted");
test(typeContainer2.runtimeTypeNames(baseTypeName) == expectedRuntimeTypeNames,
"Run-time type information was not kept up-to-date during runtime erasure.");
// Ensure traditional erasure with no runtime information still succeeds.
test(typeContainer2.erase<float>(), "Did not erase float object.");
test(typeContainer2.size() == expectedKeys.size() - 3, "Failed to erase 2 keys.");
return 0;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment