3. Instruct Overrides
After inserting the override implementations, you can instruct the override instance to control what the override functions do when called.
Instructing Referenceable Function
CO_GLOBAL
can be used as scope
when you are trying to instruct global function.
All the actions are defined in OverrideInfoSetter
for normal overrides and
OverridePassthroughInfoSetter
for passthrough. The detail for each action is covered later in this
documentation.
Example
//int FreeFunction(int value1, float value2)
CO_INSTRUCT_REF(MyOverrideInstance, CO_GLOBAL, FreeFunction)
.Returns<int>(42);
//template<typename T>
//int FreeTemplateFunction(T value1)
CO_INSTRUCT_REF(MyOverrideInstance, CO_GLOBAL, FreeTemplateFunction<int>)
.Returns<int>(42);
//int MyClass::MemberFunction(int value1, float value2);
CO_INSTRUCT_REF(MyOverrideInstance, MyClass, MemberFunction)
.Returns<int>(42);
//template<typename T>
//int MyTemplateClass<T>::AnotherMemberFunction(int value1, float value2);
CO_INSTRUCT_REF(MyOverrideInstance, MyTemplateClass<int>, AnotherMemberFunction)
.Returns<int>(42);
Important
Here are the things we look at when selecting which override instruction to use:
- Function name (
__func__
) - Argument Types
- Return Types
- Additional action requirement such as
If
,WhenCalledWith
, etc.
Important
The scope
is only used for referencing the function so that it verify if the function exists
or not, as well as allowing renaming for LSP. It is NOT used for filtering override
functions.
Instructing Non Referenceable Function
There is a limitation to this where some type of functions cannot be reference with CO_INSTRUCT_REF
.
In this case, you will need to use CO_INSTRUCT_NO_REF
variant.
You should use this macro variant for:
- Unreferencable function, such as ones in anonymous namespace or not included in the file which
performs
CO_INSTRUCT
- Overloaded functions
- Constructor, Destructor
- Complexly declared function
If you encountered one that you think it should work with CO_INSTRUCT_REF
, please let us know.
Instructing Passthrough
Functions without explicit instructions are called passthrough, and is recorded everytime when it
happens. Only a limited subset of actions are allowed for passthrough but it works the same as
the normal CO_INSTRUCT
macros.
Removing Instructs
Here are the macros for removing instructs.
CO_REMOVE_INSTRUCT_REF(overrideInstance, scope, functionName);
CO_REMOVE_INSTRUCT_NO_REF(overrideInstance, functionName);
CO_REMOVE_INSTRUCT_PASSTHROUGH(overrideInstance);
CO_CLEAR_ALL_INSTRUCTS(overrideInstance);
Action Chaining
Actions can be chained together in any order:
CO_INSTRUCT_REF(MyOverrideInstance, CO_GLOBAL, MyFunction)
.WhenCalledWith(42, 3.14f)
.SetArgs<CO_ANY_TYPE, float&>(CO_DONT_SET, 2.71f)
.Returns<int>(100)
.Times(2)
.Expected();
Actions: Returns
Returns a specific value (Returns
):
Returns void (early return) (ReturnsVoid
):
Returns using a function (ReturnsByAction
):
.ReturnsByAction<ReturnType>(std::function<TypedDataInfo(void* instance,
const std::vector<TypedDataInfo>& args,
cosnt TypedInfo& returnInfo)>)
TypedDataInfo.hpp
#ifndef CO_TYPED_DATA_INFO_HPP
#define CO_TYPED_DATA_INFO_HPP
#include "PureType.hpp"
#include <typeinfo>
#include <cstddef>
#include <memory>
namespace CppOverride
{
struct TypedDataInfo
{
std::shared_ptr<void> ManagedData = nullptr;
void* UnmanagedData = nullptr;
std::size_t TypeHash = 0;
bool IsSet = false;
bool Managed = false;
template<typename T>
inline TypedDataInfo& CreateReference(INTERNAL_CO_UNREF(T)* dataPtr)
{
UnmanagedData = dataPtr;
TypeHash = typeid(T).hash_code();
IsSet = dataPtr != nullptr;
Managed = false;
return *this;
}
template<typename T>
inline TypedDataInfo& CreateValue(T data)
{
ManagedData = std::shared_ptr<void>(new INTERNAL_CO_UNREF(T)(data));
TypeHash = typeid(T).hash_code();
IsSet = true;
Managed = true;
return *this;
}
template<typename T>
inline INTERNAL_CO_UNREF(T)* GetTypedDataPtr() const
{
return static_cast<INTERNAL_CO_UNREF(T)*>(Managed ? ManagedData.get() : UnmanagedData);
}
template<typename T>
inline bool IsType() const
{
return IsSet && (TypeHash == typeid(T).hash_code());
}
};
}
#endif
TypedInfo.hpp
#ifndef CO_TYPED_INFO_HPP
#define CO_TYPED_INFO_HPP
#include <typeinfo>
#include <cstddef>
namespace CppOverride
{
struct TypedInfo
{
std::size_t TypeHash = 0;
bool IsSet = false;
template<typename T>
inline TypedInfo& Create()
{
TypeHash = typeid(T).hash_code();
IsSet = true;
return *this;
}
template<typename T>
inline bool IsType() const
{
return IsSet && (TypeHash == typeid(T).hash_code());
}
};
}
#endif
Example
//Return a specific value
CO_INSTRUCT_REF(MyOverrideInstance, CO_GLOBAL, MyFunction)
.Returns<int>(42);
//Return void early
CO_INSTRUCT_REF(MyOverrideInstance, CO_GLOBAL, MyVoidFunction)
.ReturnsVoid();
//Return using lambda
CO_INSTRUCT_REF(MyOverrideInstance, CO_GLOBAL, MyFunction)
.ReturnsByAction<int>
(
[](void* instance,
const std::vector<TypedDataInfo>& args,
const TypedInfo& returnInfo) -> TypedDataInfo
{
//NOTE: instance can be nullptr if it is a free function
if(returnInfo.IsType<int>())
return TypeDataInfo().CreateValue<int>(100);
return TypeDataInfo();
}
});
//Return a reference using lambda
int returnValue = 0;
CO_INSTRUCT_REF(MyOverrideInstance, CO_GLOBAL, MyFunction)
.ReturnsByAction<int&>
(
[&returnValue](void* instance,
const std::vector<TypedDataInfo>& args,
const TypedInfo& returnInfo) -> TypedDataInfo
{
if(returnInfo.IsType<int&>())
return TypeDataInfo().CreateReference<int&>(&returnValue);
return TypeDataInfo();
}
});
Actions: Arguments
Set specific argument values (SetArgs
):
Set arguments using a function (SetArgsByAction
):
.SetArgsByAction<ArgType1, ArgType2, ...>(std::function<void(void* instance,
std::vector<TypedDataInfo>& args)>)
instance
can be nullptr
if it is a free function.
Use CO_ANY_TYPE
and CO_DONT_SET
to skip certain arguments.
Example
//Set specific values
CO_INSTRUCT_REF(MyOverrideInstance, CO_GLOBAL, MyFunction)
.SetArgs<int, float&>(42, 3.14f);
//Skip first argument, set second
CO_INSTRUCT_REF(MyOverrideInstance, CO_GLOBAL, MyFunction)
.SetArgs<CO_ANY_TYPE, float&>(CO_DONT_SET, 3.14f);
//Set using lambda
CO_INSTRUCT_REF(MyOverrideInstance, CO_GLOBAL, MyFunction)
.SetArgsByAction<int, float&>
(
[](void* instance, std::vector<TypedDataInfo>& args)
{
//NOTE: instance can be nullptr if it is a free function
(void)instance;
if(args[0].IsType<int>())
*args[0].GetTypedDataPtr<int>() = 42;
if(args[1].IsType<float&>())
*args[1].GetTypedDataPtr<float&>() = 3.14f;
}
);
Actions: Conditions
Match specific argument values (WhenCalledWith
):
Custom condition using function (If
):
Use CO_ANY
to match any value for specific arguments.
Example
//Match specific values
CO_INSTRUCT_REF(MyOverrideInstance, CO_GLOBAL, MyFunction)
.WhenCalledWith(42, 3.14f)
.Returns<int>(100);
//Match any value for second argument
CO_INSTRUCT_REF(MyOverrideInstance, CO_GLOBAL, MyFunction)
.WhenCalledWith(42, CO_ANY)
.Returns<int>(100);
//Custom condition
CO_INSTRUCT_REF(MyOverrideInstance, CO_GLOBAL, MyFunction)
.If
(
[](void* instance, const std::vector<TypedDataInfo>& args)
{
//NOTE: instance can be nullptr if it is a free function
(void)instance;
return args[0].IsType<int>() && *args[0].GetTypedDataPtr<int>() > 10;
}
)
.Returns<int>(100);
Actions: Controls
Limit number of times override is triggered (Times
):
Match specific object instance (for member functions) (MatchesObject
):
Match any object instance (MatchesAny
):
Example
Actions: Callbacks
Execute code when override is triggered (WhenCalledExpectedly_Do
):
Execute code when override is NOT triggered (Otherwise_Do
):
An override can fail to trigger due to override conditions not met or having error while overriding.
See 4. Inspect Override Expectations on getting details regarding the failure of triggering override.
Example
bool wasCalled = false;
CO_INSTRUCT_REF(MyOverrideInstance, CO_GLOBAL, MyFunction)
.WhenCalledWith(42)
.Returns<int>(100)
.WhenCalledExpectedly_Do
(
[&wasCalled](void*, std::vector<TypedDataInfo>&)
{
wasCalled = true;
}
)
.Otherwise_Do
(
[](void*, std::vector<TypedDataInfo>&)
{
std::cout << "Override condition not met!" << std::endl;
}
);
Actions: Expectations And Results
Mark override as expected to satisfy all conditions (Expected
):
Note
This means that if there's a Times(count)
condition, the override will be expected to be
triggered exactly count
times. Otherwise, the override will be expected to be triggered at
least once.
Mark override as expected NOT to satisfy all conditions (ExpectedNotSatisfied
):
Warning
Expected
and ExpectedNotSatisfied` are mutually exclusive. Only one of them is true.
So if you have Times(count)
condition, ExpectedNotSatisfiedwill be true if the override is
triggered not **EXACTLY**
count` times.
Get override result (AssignsResult
):
CppOverride::ResultPtr result; //using ResultPtr = std::shared_ptr<OverrideResult>;
.AssignsResult(result)
For more information about CppOverride::ResultPtr
, see Override Result section.
Example
CppOverride::ResultPtr result;
CO_INSTRUCT_REF(MyOverrideInstance, CO_GLOBAL, MyFunction)
.WhenCalledWith(42)
.Returns<int>(100)
.AssignsResult(result)
.Expected();
MyFunction(42); //Should succeed
assert(result->LastStatusSucceed());
MyFunction(99); //Should fail condition
assert(result->GetLastStatus() == CppOverride::OverrideStatus::MATCHING_CONDITION_VALUE_FAILED);
Passthrough Actions
For CO_INSTRUCT_PASSTHROUGH
, only a subset of actions are available: