user2746401
user2746401

Reputation: 3426

Ensure that class derived from parent CRTP class implements function

Brief:

I want to make sure a derived class implements a member function required by a function within the parent CRTP class.

Detail:

I have some code like this

class Base
{
public:
    class Params
    {
    public:
        virtual ~Params() {}
    };

    virtual void myFunc( Params& p ) = 0;
};

template< typename T >
class CRTP : public Base
{
public:
    virtual void myFunc( Base::Params& p ) override
    {
        typename T::Params& typedParams = dynamic_cast<typename T::Params&>( p );
        static_cast<T*>( this )->myFunc( typeParams );
    }

};

class Imp : public CRTP<Imp>
{
public:
    class Params : public CRTP<Imp>::Params
    {
    public:
        virtual ~Params() {}

        int x, y, z;
    };

    virtual void myFunc( Imp::Params& p );
};

The intention is that I can have multiple Imp child classes all doing different things in myFunc and accepting their own required parameters. The interface provided by Base is then utilized by higher level functions that only need to have a pointer/reference of type Base::Params and Base. My problem is making sure that any Imp provides a specialized myFunc. To avoid infinite recursion Imp must implement myFunc.

My first try was adding a pure virtual function to CRTP

virtual void myFunc( typename T::Params& p ) = 0;

but that doesn't work as Imp hasn't been fully defined when CRTP is being defined. This question uses a static_assert which made me think of doing the same with the static_assert within CRTP::myFunc. Except I'm not sure what should be the expression in the static assertion for a non-static function.

  1. Can I use a static_assert for what I need?
  2. Is that the best/cleanest way to ensure the derived class has the needed function?
  3. Have I got carried away with my class design and there is a better way of doing things?

Thanks.

Upvotes: 10

Views: 2021

Answers (3)

user2249683
user2249683

Reputation:

You may break dynamic polymorphism and switch to static polymorphism:

#include <iostream>
#include <type_traits>

class Base
{
    public:
    class Params
    {
        public:
        virtual ~Params() {}
    };

    virtual ~Base() {}
    virtual void myFunc(Params& p) = 0;
};


namespace Detail {
    // Helper for the static assertion
    // Omit this if "‘void CRTP<T>::myFunc(Base::Params&) [with T = Imp]’ is private" is good enough
    struct is_MyFunc_callable_implementation
    {
        template<typename Object, typename Params>
        static decltype(std::declval<Object>().myFunc(std::declval<Params&>()), std::true_type())
        test(int);

        template<typename Object, typename Params>
        static std::false_type
        test(...);
    };

    template<typename Object, typename... A>
    using is_MyFunc_callable = decltype(is_MyFunc_callable_implementation::test<Object, A...>(0));

    // Helper function to break recursion
    template<typename Object, typename Params>
    inline void invokeMyFunc(Object& object, Params& params) {
        static_assert(is_MyFunc_callable<Object, Params>::value, "The derived class is missing 'MyFunc'");
        object.myFunc(params);
    }
} // namespace Detail

template<typename T>
class CRTP: public Base
{
    private:
    // Make this final!
    virtual void myFunc(Base::Params& p) override final
    {
        static_assert(std::is_base_of<Base, T>::value, "T must derive from CRTP");
        typename T::Params& typeParams = dynamic_cast<typename T::Params&>(p);
        Detail::invokeMyFunc(static_cast<T&>(*this), typeParams);
    }
};

class Imp: public CRTP<Imp>
{
    public:
    class Params: public CRTP<Imp>::Params
    {
        public:
        int x = 1;
        int y = 2;
        int z = 3;
    };

    // Without this function:
    // error: static assertion failed: The derived class is missing 'MyFunc'
    // error: ‘void CRTP<T>::myFunc(Base::Params&) [with T = Imp]’ is private
    #if 0
    void myFunc(Params& p) {
        std::cout << p.x << p.y << p.z << '\n';
    }
    #endif
};

int main()
{
    Imp imp;
    Base* base = &imp;
    Imp::Params params;
    base->myFunc(params);
}

However, my opinion is: The base class design is a failure and the code above is just a work around.

Upvotes: 1

Walter
Walter

Reputation: 45444

The idea of using a different name for the member of derived classes (as in Rudolfs Bundulis answer) is good. However, I would make this a protected method so that users are not tempted to try using it.

Also, using Derived::Params in the CRTP base raises additional complications (since Derived=Imp is not fully declared at point of its use in CRTP<Imp>), so best keep Base::Params as the function parameter throughout.

struct Base                                          // public user interface
{
  struct Params { virtual ~Params() {} };
  virtual void myFunc( Params& ) = 0;
};

namespace details {                                  // deter clients from direct access
  template< typename Derived >
  struct CRTP : Base
  {
    virtual void myFunc( Params& p ) final           // cannot be overridden
    {
      static_cast<Derived*>( this )->myFuncImp(p);
    }
  };

  class Imp : public CRTP<Imp>
  {
    struct Params : CRTP<Imp>::Params { int x, y, z; };
    void myFuncImpDetails( Params* );
  protected:                                         // protected from clients
    void myFuncImp( Base::Params& p )
    {
      auto pars=dynamic_cast<Params*>(&p);
      if(pars)
        myFuncImpDetails(pars);
      else
        throw std::runtime_error("invalid parameter type provided to Imp::myFunc()");
    }
  };
} // namespace details

Upvotes: 0

Rudolfs Bundulis
Rudolfs Bundulis

Reputation: 11944

Why not just use a different name for the function? Then you will have a compilation error for each derivation of CRTP class without and implementation. Consider this:

class Base
{
public:
    class Params
    {
    public:
        virtual ~Params() {}
    };

    virtual void myFunc( Params& p ) = 0;
};

template< typename T >
class CRTP : public Base
{
public:
    virtual void myFunc( Base::Params& p ) final override
    {
        typename T::Params& typedParams = dynamic_cast<typename T::Params&>( p );
        static_cast<const T*>( this )->myFuncImp( typedParams );
    }

};

class Imp : public CRTP<Imp>
{
public:
    class Params : public CRTP<Imp>::Params
    {
    public:
        virtual ~Params() {}

        int x, y, z;
    };
};

int main(int argc, char** argv)
{
    Imp imp;
}

Compilation fails since there is no myFuncImp provided by Imp.

Upvotes: 6

Related Questions