Home > Software engineering >  Compile-time check for existence of a template specialization of a function using C 20 requires exp
Compile-time check for existence of a template specialization of a function using C 20 requires exp

Time:11-12

I am developing a sort of event system where the event listeners are determined at compile-time. To achieve this, I need a function which can tell me whether the parameter class T implements a specific specialization of the OnEvent() function.

My current attempt uses a C 20 requires expression:

template<class T>
class ScriptComponent
{
    static_assert(std::is_base_of_v<Script, T>, "T must derive from Script!");

private:
    T m_ScriptInstance;

public:
    // Should return true if t.OnEvent<E>(e) can compile, false otherwise.
    template<typename E>
    constexpr static bool ImplementsEventFunction()
    {
        constexpr bool isImplemented = requires(const T& t, const E& e)
        {
            t.OnEvent<E>(e);
        };
        return isImplemented;
    }
};

Example of a class which can listen to events:

class SomeScript : public Script
{
public:
    // delete any non-specialized templates
    template<typename E>
    void OnEvent(const E&) = delete;

    // the template specialization I want to check for
    template<>
    void OnEvent<SomeEvent>(const SomeEvent& e)
    {
        // do stuff
    }
};

Usage of ImplementsEventFunction() :

// should return true (there is a OnEvent() specialization for SomeEvent)
constexpr bool a = ScriptComponent<SomeScript>ImplementsEventFunction<SomeEvent>();
// should return false (there is no OnEvent() specialization for SomeOtherEvent)
constexpr bool b = ScriptComponent<SomeScript>ImplementsEventFunction<SomeOtherEvent>();

ImplementsEventFunction() always returns false no matter what the template parameters are. Obviously, I seem to be using the requires expression wrong, but I cannot find my mistake.

CodePudding user response:

There is no reason to require OnEvent to be implemented as a template. This feels like being overly-controlling of the user's code. Concepts are not for telling the user how exactly to implement something. You are going to call an interface in a certain way, and users should implement their code such that that call syntax is valid.

And your code ought to be using t.OnEvent(e). There is no reason why your code needs to specify the template argument explicitly at the call site. Users should be able to implement OnEvent for some particular E as a non-template function (or multiple non-template overloads), or as a template function with template argument deduction taking care of the template parameter.

As such, the proper concept would be:

template<typename T, typename E>
concept has_event_for = requires(T t, E e)
{
  t.OnEvent(e);
};

class SomeScript : public Script
{
public:
  void OnEvent(const SomeEvent& e)
  {
      // do stuff
  }

  //No need to delete anything or use templates.
};

Don't treat concepts like you would base classes, where you explicitly spell out the exact parameters which derived class interfaces must exactly follow.


If however you absolutely must do this, you have to use the template keyword at the call site:

template<typename T, typename E>
concept has_event_for = requires(T t, E e)
{
  t.template OnEvent<E>(e);
};

This is yet another reason not to do it this way.

  • Related