10

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.

Community
  • 1
  • 1
user2746401
  • 3,157
  • 2
  • 21
  • 46
  • can you not use some SFINAE magic to establish whether `Imp::Param` is different from `Base::Param` and that `Imp::myFunc()` takes `Imp::Param` as argument? – Walter Sep 02 '15 at 13:35
  • @Walter for the second, if there is inheritance, it will have false positives. For the first, you might not want to require that. – Yakk - Adam Nevraumont Sep 02 '15 at 13:50
  • 1
    Aside: why are you static casting to `T const*` from a non-`const` method? Either `myFunc` should be `const` in `Base` and `CRTP`, or you should call `static_cast` in the `CRTP` implementaiton. – Yakk - Adam Nevraumont Sep 02 '15 at 13:52
  • @Yakk The false positives don't matter, as long as there is a method `Imp::myfunc(Imp::Params&)`, we are sorted, aren't we? – Walter Sep 02 '15 at 13:55
  • @Walter That method could be the virtual one in `Base`. Unless you are arguing we examine a method pointer, which seems like a bad idea to me: false negatives and/or over restriction on what can happen. – Yakk - Adam Nevraumont Sep 02 '15 at 13:56
  • @Yakk - fixed casting – user2746401 Sep 02 '15 at 14:19
  • I tried to use a detection idiome to check if the implementation can be overwritten by another implementation, if both implementations are marked as final the check would fail. But it threw compile errors rather then a substitution failure. Maybe someone can reuse it: http://pastebin.com/GR1r1K9g – Naios Sep 13 '15 at 19:49
  • Can you explain the problem with some code. I feel that what you have given in your question is a your hand made solution. I feel your question somewhat related to my question: [Force all classes to implement / override a 'pure virtual' method in multi-level inheritance hierarchy](http://stackoverflow.com/questions/9477581/force-all-classes-to-implement-override-a-pure-virtual-method-in-multi-level) – iammilind Sep 17 '15 at 05:10

3 Answers3

6

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.

Rudolfs Bundulis
  • 11,636
  • 6
  • 33
  • 71
1

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.

  • Could you elaborate on the "failure" of the base class design? How could it be improved? – user2746401 Sep 16 '15 at 14:46
  • @user2746401 I do not imagine any use case for this. The closest is event handling, but that should not switch to static polymorphism. –  Sep 16 '15 at 18:12
0

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
Walter
  • 44,150
  • 20
  • 113
  • 196
  • "the unwieldy dynamic_cast<>" is rather needed as there is no way to know if the passed p is of the correct type. The point of CRTP is to avoid duplicating the dynamic casting in the (multiple) Imp classes. – user2746401 Sep 15 '15 at 16:15
  • Oh, and making the function protected (on its own) doesn't work as CRTP has no access to private/protected members in Imp. However, I added CRTP as a friend to Imp to work around this. – user2746401 Sep 15 '15 at 16:21