简体   繁体   English

如何检查在编译时是否调用了模板化方法?

[英]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;
}

** **

TL;DR TL; DR

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.

相关问题 如果可以使用一组特定参数调用的函数存在,如何在编译时检查? - How to check at compile-time if a function that can be called with a specific set of arguments exists? 如何使用可选的编译时参数声明模板化函数? - How to declare a templated function with an optional compile-time parameter? 如何使用编译时信息动态创建模板类? - How to dynamically create templated classes with compile-time information? 如何 map 通用模板化编译时函数 - How to map generic templated compile-time functions 构造模板类型的编译时列表? - Constructing a compile-time list of templated types? C ++编译时检查是否可以使用某种类型的参数调用重载函数 - C++ Compile-time check that an overloaded function can be called with a certain type of argument 如何确定指针转换将被偏移的编译时间 - How can I determine compile-time that a pointer cast will be offsetted 如果调用 function 的特定重载,如何引发编译时错误? - How to provoke a compile-time error if a specific overload of a function is called? 如何实现编译时检查转发在CRTP中是否有效? - How to implement a compile-time check that a downcast is valid in a CRTP? 如何在编译时检查类是否具有继承的函数? - How to check if a class has an inherited function at compile-time?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM