Reputation: 6415
How to enclose std::is_base_of
in a std::function
practically?
I don't think it is possible, because the type is erased.
How to workaround? I want to cache std::function f=check whether X derived from B
.
It will be called later.
#include <iostream>
#include <type_traits>
#include <functional>
using namespace std;
class B{ };
class C: B{};
int main(){
std::cout<<std::is_base_of<B,C>()<<std::endl; //print 1
std::function<bool()> f=[](X)->bool{ //<--- syntax error (X is class name)
return std::is_base_of<B,X>();
};
std::cout<<f(C)<<std::endl; //<--- possible? (should print 1)
return 0;
}
In real case, f
is called in a far-away location of code, and I don't want to instantiate B
or C
.
(Thus, I can't use dynamic_cast
to check.)
I am trying to get a 2D-bool table (result #
) for some sophisticated types manipulation.
class B{};
class C : B{};
class D{};
int main(){
for(auto cachefunction : cacheFunctions){
cachefunction();
//^ "cacheFunctions" is {register<B>(), register<C>(), register<D>()}
}
//in real case, cacheFunctions.size() ~ 200+
auto result=.... //#
}
I can edit code inside register<T>()
to whatever I want, but I can't request user to call register<T1,T2>
for every possible tuple.
Roughly speaking, the result #
is an array of bool
flag whether T2
derived from T1
.
B C D (parent)
------------
(derived)
B x 0 0
C 1 x 0
D 0 0 x (x=don't care, 0 or 1 are OK)
int[]/std::vector<int> result
= {x,0,0 , 1,x,0 , 0,0,x}
.
My ultimate goal is to get the table at the line //#
.
I have a user code that calls my library like this.
These lines are scattering around many user's .cpp
:-
requestSystem<S1>()->someBFunction();
requestSystem<S2>()->someCFunction();
....
Si
are sub-systems in my library (Here, I use composition rather that inheritance.).
So far, I have successfully used some trick (array of std::function
) to instantiate those sub-system (new Si()
), before those functions are actually called at run-time. Thus, it runs fine.
As my program grow, more Si
are born.
I notice that there are appropriate cases where some Sy
are best to inherit from a certain Sx
.
In such cases, I find that if I instantiate both new Sx()
and new Sy()
, my program will act strangely, because there are two instance of base class Sx
(it should be singleton by design).
I think it would be nice if I can detect such cases automatically by embedding some additional code inside requestSystem<T>()
to instantiate only new S_Y()
, and let requestSystem<S_X>()
return the same pointer to S_Y
.
I can't use the same trick (array of std::function
) for std::is_base_of
to check inheritance because the type is erased. Moreover, I intend to not call new S_X()
, so I can't cache the type by its instance and use dynamic_cast
later.
Here is MCVE (ideone).
The first part is Manager's definition:-
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
class Manager{
public: Manager(){std::cout<<"Manager"<<std::endl; }
};
class EntityManager : public Manager{
public: EntityManager(){std::cout<<"EntityManager"<<std::endl;}
};
class AdvanceEntityManager : public EntityManager{
public: int testField=5; //just for testing
public: AdvanceEntityManager(){
std::cout<<"AdvanceEntityManager"<<std::endl;
}
};
Here is the type-manipulator :-
template<class T> class DummyHook{ public: static int dummyInt; };
template<class T> class IndexCache{ public: static int index; };
//^ index = index of T* inside "globalManagerList"
std::vector<Manager*> globalManagerList;
std::vector<std::function<Manager*(int)>>* getFunctionPointers(){
static std::vector<std::function<Manager*(int)>> cacher;
return &cacher;
}
/** it is called indirectly by "requestSystem" */
template<class T> int indirectCall(){
std::function<Manager*(int)> func=[](int assignIndex){
IndexCache<T>::index = assignIndex;
auto re= new T();
globalManagerList.push_back(re);
return re;
};
getFunctionPointers()->push_back(func);
int dummy=42;return dummy;
}
template<class T> T* requestSystem(){
int k=DummyHook<T>::dummyInt;
//^ optimized out, but force calling "indirectCall()" before main() at @
return static_cast<T*>(globalManagerList[IndexCache<T>::index]);
}
template<class T> int DummyHook<T>::dummyInt = indirectCall<T>(); //@
template<class T> int IndexCache<T>::index = -42;
Here is the main function :-
int main() {
auto fs=getFunctionPointers();
int n=0;
for(auto ele: *fs){
ele(n); ++n;
//^ call every function in
// static std::vector<std::function<Manager*(int)>> cacher
}
std::cout<<"All initialized, ready!"<<std::endl;
auto entityManagerPtr=requestSystem<EntityManager>();
auto advanceManagerPtr=requestSystem<AdvanceEntityManager>();
//^ In this program, they are different instance, but I want it to be the same instance.
std::cout<<"Should be 5 : "<<advanceManagerPtr->testField<<std::endl;
return 0;
}
This is the output (both managers are instantiated :(
):-
Manager
EntityManager
Manager
EntityManager
AdvanceEntityManager
All initialized, ready!
Should be 5 : 5
Upvotes: 1
Views: 187
Reputation: 275190
The list of bases of a type is not avaliable at runtime, which is what your code in effect requires.
We can however write our own typesystem. In this case, we mark up each type with a list of each parent, and enable compile time reflection over that list.
Then when we set up the factories, we also replace the factories for the parent types, and ensure that only the child type is used.
Here is some random tools to help:
template<class...Ts> struct types_t {};
template<class T> struct tag_t { constexpr tag_t() {} using type=T; };
template<class T> constexpr tag_t<T> tag{};
template<class Tag> using type_t=typename Tag::type;
template<std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
return [](auto&& f)->decltype(auto) {
return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto index_upto( std::integral_constant<std::size_t, N> ={} ) {
return index_over( std::make_index_sequence<N>{} );
}
template<class F>
auto invoke_foreach( F&& f ) {
return [f=std::forward<F>(f)](auto&&...args){
using discard=int[];
(void)discard{0,(void(
f(decltype(args)(args))
),0)...};
};
}
template<class F>
auto invoke_on_tuple( F&& f ) {
return [f=std::forward<F>(f)](auto&& tuple)->decltype(auto) {
using Tuple = decltype(tuple);
using dTuple = std::decay_t<Tuple>;
using Size = std::tuple_size<dTuple>;
return index_upto<Size{}>()( [&](auto&&...args)->decltype(auto){
return f( std::get<decltype(args){}>( decltype(tuple)(tuple) )... );
});
};
}
template<class...Ts>
constexpr std::tuple< tag_t<Ts>... > as_tuple_tags( types_t<Ts...> ) { return std::make_tuple(tag<Ts>...); }
template<class F>
struct y_combinator_t {
F f;
template<class...Args>
decltype(auto) operator()( Args&&... args ) {
return f(*this, std::forward<Args>(args)...);
}
};
template<class F>
y_combinator_t<std::decay_t<F>> y_combinate( F&& f ) { return {std::forward<F>(f)}; }
Now, we mark up types with their parents:
class Manager{
public: Manager(){std::cout<<"Manager"<<std::endl; }
};
class EntityManager : public Manager{
public:
int base_value = 3;
EntityManager(){std::cout<<"EntityManager"<<std::endl;}
using parents = types_t<Manager>;
};
class AdvanceEntityManager : public EntityManager{
public: int testField=5; //just for testing
public:
AdvanceEntityManager(){
std::cout<<"AdvanceEntityManager"<<std::endl;
base_value = 1;
}
using parents = types_t<EntityManager>;
};
and use the starting code to let us easily work with parents:
template<class T, class Parents = typename T::parents>
auto foreach_parent( tag_t<T> ) {
constexpr auto parent_tuple = as_tuple_tags( Parents() );
return [](auto&& f) {
return invoke_on_tuple(invoke_foreach(f))(decltype(parent_tuple){});
};
}
template<class T, class...Ts>
auto foreach_parent( tag_t<T>, Ts&&... ) {
return [](auto&& f) {};
}
we set up a two-entry cache, a singleton factory, and use smart pointers:
template<class T> class IndexCache{
public:
static int index;
static int factory;
};
template<class T> class Dummy{ public: static int index; };
using pManager = std::shared_ptr<Manager>;
using ManagerFactory = std::function<pManager()>;
using Task = std::function<void()>;
std::vector<pManager> globalManagerList;
std::vector<Task>& getManagerFactories(){
static std::vector<Task> cacher{};
return cacher;
}
template<class T>
ManagerFactory singleton_factory() {
return []{
static auto single = (void(std::cout << "making " << typeid(T).name() << " singlton" << std::endl), std::make_shared<T>());
return single;
};
}
and modify indirect call to replace the parent factory tasks:
template<class T>
void indirectCall(){
std::cout << "Setting up " << typeid(T).name() << std::endl;
auto func=[](auto tag){
return [tag](){
IndexCache<type_t<decltype(tag)>>::index = globalManagerList.size();
globalManagerList.push_back(singleton_factory<T>()());
};
};
//std::cout << "Adding " << typeid(T).name() << " factory " << std::endl;
IndexCache<T>::factory = getManagerFactories().size();
getManagerFactories().push_back(func(tag<T>));
auto replace_parents = y_combinate(
[&](auto& replace_parents, auto child_tag) {
foreach_parent(child_tag)([&](auto parent_tag){
using Parent = type_t<decltype(parent_tag)>;
std::cout << "Replacing " << typeid(Parent).name() << " factory with " << typeid(T).name() << " factory" << std::endl;
getManagerFactories()[IndexCache<Parent>::factory] = func(tag<Parent>);
replace_parents( parent_tag );
});
}
);
replace_parents(tag<T>);
std::cout << "Added " << typeid(T).name() << " factory " << std::endl;
}
In requestSystem
, we ensure that all of the setup has been done:
void setup_code() {
for (auto&& factory:getManagerFactories())
factory();
}
void setup() {
static int unused = (setup_code(),7);
(void)unused;
}
template<class T>
T* requestSystem()
{
int dummy = Dummy<T>::index;
(void)dummy;
std::cout << "Requesting " << typeid(T).name() << std::endl;
setup();
return static_cast<T*>(globalManagerList[IndexCache<T>::index].get());
}
template<class T> int IndexCache<T>::index = -1;
template<class T> int IndexCache<T>::factory = -1;
template<class T> int Dummy<T>::index = (indirectCall<T>(), 7);
and then we test it:
int main() {
std::cout<<"All initialized, ready!"<<std::endl;
auto entityManagerPtr=requestSystem<EntityManager>();
//std::cout<<"Phase 1"<<std::endl;
(void)entityManagerPtr;
auto advanceManagerPtr=requestSystem<AdvanceEntityManager>();
//std::cout<<"Phase 2"<<std::endl;
//^ In this program, they are different instance, but I want it to be the same instance.
std::cout<<"Should be 5 : "<<advanceManagerPtr->testField<<std::endl;
std::cout<<"Should be 1 : "<<entityManagerPtr->base_value<<std::endl;
return 0;
}
here we ask for the entityManagerPtr
, and we get the advanceManagerPtr
. We can see from the logs that only one object was created, and its entityManagerPtr->base_value was 1.
Upvotes: 1