[英]How can I check if a templated method was called at compile-time?
I am writing an entity entity component system game engine . 我正在编写一个实体实体组件系统游戏引擎 。 As a part of this, I have written a Manager
class which will register various IBase
implementations and, later, allow me to instantiate these implementation. 作为其中的一部分,我编写了一个Manager
类,它将注册各种IBase
实现,稍后,允许我实例化这些实现。 See below for an example of how I wish to use this. 请参阅下面的示例,了解我希望如何使用它。
class Manager{
public:
template<class T>
void registerDerived()
{ /*Register a Derived with the Manager*/ };
template<class T>
T createDerived()
{ /*if T is not registered, throw an error*/
return T();};
};
struct IBase{
};
struct Derived1 : public IBase{
};
struct Derived2 : public IBase{
};
As noted in the comments, I have code in template<class T>Manager::createDerived()
which checks whether or not a particular implementation of Base
has been registered using template<class T>Manager::registerDerived()
, and if it has not been registered it throws an error. 如注释中所述,我在template<class T>Manager::createDerived()
有代码,它检查是否已使用template<class T>Manager::registerDerived()
注册了Base
的特定实现,如果是尚未注册它会抛出错误。 This check is trivial and was left out of the code sample to keep things simple. 这个检查是微不足道的,并且不在代码示例中以保持简单。
Here is my question: is it possible to move this check to compile-time, rather than waiting until runtime? 这是我的问题:是否可以将此检查移至编译时,而不是等到运行时? It seems like there should be enough information at runtime to make this determination. 似乎在运行时应该有足够的信息来做出这个决定。
So far, I've explored/read about SFINAE, which seems like the approach to take, but I cannot figure out how to make these idioms work in this specific case. 到目前为止,我已经探索/阅读了SFINAE,这似乎是采取的方法,但我无法弄清楚如何使这些习语在这种特定情况下起作用。 This link gives a good overview of the basic SFINAE idiom, this SO question gives some good code snippets, and finally This blog post seems to address almost my exact situation. 这个链接很好地概述了基本的SFINAE习语, 这个SO问题提供了一些很好的代码片段,最后这篇博文似乎几乎解决了我的确切情况。
Here is a full example which is my attempt to implement the information found in these links: 以下是我尝试实现这些链接中的信息的完整示例:
#include <iostream>
class Manager{
public:
template<class T>
void registerDerived()
{ /*Register a Derived with the Manager*/ }
template<class T>
T createDerived()
{ /*if T is not registered, throw an error*/
return T();}
};
struct IBase{
};
struct Derived1 : public IBase{
};
struct Derived2 : public IBase{
};
template<typename T>
struct hasRegisterDerivedMethod{
template <class, class> class checker;
template <typename C>
static std::true_type test(checker<C, decltype(&Manager::template registerDerived<T>)> *);
template <typename C>
static std::false_type test(...);
typedef decltype(test<T>(nullptr)) type;
static const bool value = std::is_same<std::true_type, decltype(test<T>(nullptr))>::value;
};
int main(){
Manager myManager;
myManager.registerDerived<Derived1>();
// whoops, forgot to register Derived2!
Derived1 d1 = myManager.createDerived<Derived1>(); // compiles fine, runs fine. This is expected.
Derived2 d2 = myManager.createDerived<Derived2>(); // compiles fine, fails at runtime (due to check in createDerived)
std::cout << std::boolalpha;
// expect true, actual true
std::cout << "Derived1 check = " << hasRegisterDerivedMethod<Derived1>::value << std::endl;
// expect false, actual true
std::cout << "Derived2 check = " << hasRegisterDerivedMethod<Derived2>::value << std::endl;
return 0;
}
** **
How can I modify the code above to produce a compile-time error (probably using static_assert
) instead of waiting until runtime to detect the error? 如何修改上面的代码以产生编译时错误(可能使用static_assert
)而不是等到运行时才能检测到错误?
** **
IMHO, you have a design issue. 恕我直言,你有一个设计问题。 The fact that registerDerived<Derived>()
is a prerequisite for a call to createDerived<Derived>()
should be expressed in code (not merely in the documentation), such that unregistered creation is impossible. registerDerived<Derived>()
是调用createDerived<Derived>()
的先决条件的事实应该用代码表示(不仅仅是在文档中),这样就不可能进行未注册的创建。
One way to achieve this is via a registration document, issued at registration and required at creation. 实现这一目标的一种方法是通过注册时发布的注册文件。 For example 例如
#include <typeinfo>
#include <typeindex>
#include <unordered_set>
struct Manager {
// serves as registration document
template<typename T>
class ticket { friend struct Manager; };
// use SFINAE to restrict T to a derived class (assumed C++14)
template<typename T>
std::enable_if_t<std::is_base_of<Manager,T>::value, ticket<T> >
registerDerived()
{
registeredTypes.insert(std::type_index(typeid(T)));
return {};
}
template<typename T, typename... Args>
T createDerived(ticket<T>, Args&&...args)
{
return T(std::forward<Args>(args)...);
}
private:
std::unordered_set<std::type_index> registeredTypes;
};
struct Derived1 : Manager {};
struct Derived2 : Manager { Derived2(int); }
int main() {
Manager manager;
auto t1 = manager.registerDerived<Derived1>();
auto t2 = manager.registerDerived<Derived2>();
auto x1 = manager.createDerived(t1);
auto x2 = manager.createDerived(t2,7);
}
Note that the object t
is likely optimized away. 请注意,对象t
可能已经过优化。
Of course, this code is different from yours, as it requires to carry the ticket<Derived>
around for any creation. 当然,这段代码与您的代码不同,因为它需要为任何创建携带ticket<Derived>
。 However, the very concept of registering followed by creation is not sensible in this simple example, as the following code would always work and do w/o prior registration (see also my question in the comments): 然而,在这个简单的例子中,注册后跟创建的概念是不明智的,因为下面的代码总是有效并且没有事先注册(参见我在评论中的问题):
template<typename T, typename...Args>
T Manager::create(Args&&..args)
{
return createDerived(register<T>(),std::forward<Args>(args)...);
}
If registration per see is a more expensive process than in my simple example, then you may check (using a unordered_set<type_index>
as above) whether a Derived
type is registered before attempting to do so. 如果每次查看注册比我的简单示例更昂贵,那么您可以在尝试之前检查(使用上面的unordered_set<type_index>
)是否注册了Derived
类型。
I don't think it's possible in a portable/reliable way. 我认为这不可能以便携/可靠的方式进行。
If your interested in compile-time only registration, I suggest to make Manager
a template class where the template parameters are the registered types. 如果您对编译时只对注册感兴趣,我建议将Manager
作为模板类,其中模板参数是注册类型。
I mean... if you write a custom type-traits as follows 我的意思是......如果你写一个自定义类型特征如下
template <typename, typename ...>
struct typeInList;
template <typename T0, typename T1, typename ... Ts>
struct typeInList<T0, T1, Ts...> : public typeInList<T0, Ts...>
{ };
template <typename T0, typename ... Ts>
struct typeInList<T0, T0, Ts...> : public std::true_type
{ using type = T0; };
template <typename T0>
struct typeInList<T0> : public std::false_type
{ };
template <typename ... Ts>
using typeInList_t = typename typeInList<Ts...>::type;
or (as suggested by Deduplicator (thanks!)) in a more compact way 或者(由Deduplicator建议(谢谢!))以更紧凑的方式
// ground case: in charge only when `typename...` variadic list
// is empy; other cases covered by specializations
template <typename, typename...>
struct typeInList : public std::false_type
{ };
template <typename T0, typename T1, typename ... Ts>
struct typeInList<T0, T1, Ts...> : public typeInList<T0, Ts...>
{ };
template <typename T0, typename ... Ts>
struct typeInList<T0, T0, Ts...> : public std::true_type
{ using type = T0; };
template <typename ... Ts>
using typeInList_t = typename typeInList<Ts...>::type;
you can use it to SFINAE enable/disable createDerived()
as follows 您可以使用它来SFINAE启用/禁用createDerived()
,如下所示
template <typename ... Ts>
struct Manager
{
template <typename T>
typeInList_t<T, Ts...> createDerived ()
{ return T(); }
};
and hasRegisterDerivedMethod
can be written as follows 和hasRegisterDerivedMethod
可以写成如下
template <typename, typename>
struct hasRegisterDerivedMethod;
template <typename ... Ts, typename T>
struct hasRegisterDerivedMethod<Manager<Ts...>, T>
: public typeInList<T, Ts...>
{ };
Unfortunately this works compile-time but not run-time so, if you need a solution that works both compile-time and run-time, this solution isn't for you. 不幸的是,这适用于编译时但不是运行时,因此,如果您需要一个兼容编译时和运行时的解决方案,此解决方案不适合您。
The following is a full working example 以下是一个完整的工作示例
#include <iostream>
template <typename, typename ...>
struct typeInList;
template <typename T0, typename T1, typename ... Ts>
struct typeInList<T0, T1, Ts...> : public typeInList<T0, Ts...>
{ };
template <typename T0, typename ... Ts>
struct typeInList<T0, T0, Ts...> : public std::true_type
{ using type = T0; };
template <typename T0>
struct typeInList<T0> : public std::false_type
{ };
template <typename ... Ts>
using typeInList_t = typename typeInList<Ts...>::type;
template <typename ... Ts>
struct Manager
{
template <typename T>
typeInList_t<T, Ts...> createDerived ()
{ return T(); }
};
struct IBase { };
struct Derived1 : public IBase{ };
struct Derived2 : public IBase{ };
template <typename, typename>
struct hasRegisterDerivedMethod;
template <typename ... Ts, typename T>
struct hasRegisterDerivedMethod<Manager<Ts...>, T>
: public typeInList<T, Ts...>
{ };
int main ()
{
Manager<Derived1> myManager;
// whoops, forgot to register Derived2!
Derived1 d1 = myManager.createDerived<Derived1>();
//Derived2 d2 = myManager.createDerived<Derived2>(); // compilation error!
std::cout << std::boolalpha;
std::cout << "Derived1 check = "
<< hasRegisterDerivedMethod<decltype(myManager), Derived1>::value
<< std::endl; // print true
std::cout << "Derived2 check = "
<< hasRegisterDerivedMethod<decltype(myManager), Derived2>::value
<< std::endl; // print false
}
Off Topic: instead of 关闭主题:而不是
static const bool value = std::is_same<std::true_type, decltype(test<T>(nullptr))>::value;
you can write 你可以写
static constexpr bool value { type::value };
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.