samedi 25 août 2018

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. 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. 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. 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.

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

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?

**

Aucun commentaire:

Enregistrer un commentaire