9

I have a plain old CRPT (please don't get distracted by access restrictions - the question is not about them):

 template<class Derived>
 class Base {
     void MethodToOverride()
     {
        // generic stuff here
     }
     void ProblematicMethod()
     {
         static_cast<Derived*>(this)->MethodToOverride();
     } 
 };

that is as usual intended to be used like this:

 class ConcreteDerived : public Base<ConcreteDerived> {
     void MethodToOverride()
     {
        //custom stuff here, then maybe
        Base::MethodToOverride();
     }
 };

Now that static_cast bothers me. I need a downcast (not an upcast), so I have to use an explicit cast. In all reasonable cases the cast will be valid since the current object is indeed of the derived class.

But what if I somehow change the hierarchy and the cast now becomes invalid?

May I somehow enforce a compile-time check that an explicit downcast is valid in this case?

sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • 1
    You do not need to have a MethodToOverride in Base class. – ysdx May 06 '11 at 06:51
  • 1
    @ysdx: I need if I want it to be optionally overridable or have some common implementation and I do want that. – sharptooth May 06 '11 at 06:58
  • 1
    But if you have the function in the base class, the call will always "work", as there **is** a function to call. – Bo Persson May 06 '11 at 07:03
  • @Bo Persson: Yes, that's why I need a downcast to have the most derived function to call and that most derived function may call the base version if it wants. – sharptooth May 06 '11 at 07:10
  • 1
    And you are not considering using a virtual function for that? :-) – Bo Persson May 06 '11 at 07:11
  • @Bo Persson: Well, virtual functions are great except they introduce a notable overhead. Here if the base version does nothing and is not overridden the compiler will see that an eliminate the call completely. That's I believe is one of the reasons CRTP is used in the first place. – sharptooth May 06 '11 at 07:14
  • @sharptooth: Even if `Base` is derived some class that's passed as `Derived`, it would further need to have a function called `MethodToOverride()` before being compilable.... All sounds unlikely in the extreme. Anyway, you might check out http://www.boost.org/doc/libs/1_44_0/libs/type_traits/doc/html/boost_typetraits/reference/is_base_of.html – Tony Delroy May 06 '11 at 07:16
  • @Sharptooth, is is possible for you to give a small example that in what cases you are expecting the cast to become invalid ? I am not able to understand that part of question. – iammilind May 06 '11 at 07:25
  • @iammilind: For example I could accidentially pass some unrelated class as the template parameter. – sharptooth May 06 '11 at 07:29
  • @sharptooth, did you mean `class Concrete : public Base` ? If that is the case, that gives an error. Or may be still I am not clear. :) – iammilind May 06 '11 at 07:41
  • possible duplicate of [How to avoid errors while using CRTP?](http://stackoverflow.com/questions/4417782/how-to-avoid-errors-while-using-crtp) – krlmlr Feb 16 '15 at 20:13

5 Answers5

5

At compile-time you can only check the static types, and that's what static_cast already does.

Given a Base*, it is only, and can only be, known at run-time what its dynamic type is, that is, whether it actually points to a ConcreteDerived or something else. So if you want to check this, it has to be done at runtime (for example by using dynamic_cast)

jalf
  • 243,077
  • 51
  • 345
  • 550
  • 1
    But to use dynamic_cast you would need to have some virtual functions, which i believe this construct tries to avoid. – Bo Persson May 06 '11 at 07:00
  • true. My point is just that this check can only be performed at runtime, which carries an associated cost, as you point out – jalf May 06 '11 at 07:20
4

For extra safety, you could add a protected constructor to Base, to make sure that something is derived from it. Then the only problem would be for the really stupid:

class ConcreteDerived : public Base<SomeOtherClass>

but that should be caught by the first code review or test case.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
3

To expand on what @Bo Persson said, you can do a compile time check in said constructor using for example Boost.TypeTraits or C++0x/11 <type_traits>:

#include <type_traits>

template<class Derived>
struct Base{
  typedef Base<Derived> MyType;

  Base(){
    typedef char ERROR_You_screwed_up[ std::is_base_of<MyType,Derived>::value ? 1 : -1 ];
  }
};

class ConcreteDerived : public Base<int>{
};

int main(){
  ConcreteDerived cd;
}

Full example on Ideone.

Xeo
  • 129,499
  • 52
  • 291
  • 397
  • 1
    Could you please replace the F-word with "you screwed it" so that a rain of downvotes and flags doesn't fall on you? – sharptooth May 06 '11 at 07:30
  • little issue here, the question is not only about making sure that the parameter `Derived` is effectively derived from `Base<..>`, but also that the current value of `this` is effectively `Derived` (or a derived type of it), which you can only check at runtime. – Matthieu M. May 06 '11 at 09:26
  • @Matthieu: My answer is an extension to @Bo Personn's, so what he said about a protected constructor holds for me too. And then, if `this` is anything else than something derived, there's something very very wrong. – Xeo May 06 '11 at 09:29
  • I agree that in CRTP `this` should be okay, but the question is asking for a way to detect when it's not. – Matthieu M. May 06 '11 at 09:48
  • unfortunately this doesn't catch the case of two classes deriving from the same CRTP base, and one of them is erroneously passing the other, eg `class ConcreteDerived1 : public Base{ }; class ConcreteDerived2 : public Base{ };`. What's even worse is that a `static_cast` to `Derived` will still compile, as the compiler 'knows how to' do the downcast. – golvok Oct 11 '19 at 19:40
2

It seems that there exists a way to check CRPT correctness at compile time.

By making Base abstract (adding some pure virtual method to Base) we guarantee that any Base instance is a part of some derived instance.

By making all Base constructors private we can prevent undesirable inheritance from Base.

By declaring Derived as friend of Base we allow the only inheritance expected by CRPT.

After this, CRPT downcast should be correct (since something is inherited from base and this "something" may be only Derived, not some other class)

Perhaps for practical purpose the first step (making Base abstract) is redundant since successful static_cast guaranties that Derived is somewhere in the Base hierarchy. This allows only an exotic error if Derived is iherited from Base <Derived> (as CRPT expects) but at the same time Derived creates another instance of Base <derived> (without inheritance) somewhere in Derived code (it can, because it is a friend). However, I doubt that someone may accidentally write such exotic code.

user396672
  • 3,106
  • 1
  • 21
  • 31
1

When you do something like below:

struct ConcreteDerived : public Base<Other>  // Other was not inteded

You can create objects of the class (derived or base). But if you try calling the function, it gives compilation error related to static_cast only. IMHO it will satisfy all practical scenarios.

If I correctly understood the question, then I feel the answer is in your question itself. :)

iammilind
  • 68,093
  • 33
  • 169
  • 336