22

Consider the following standard CRTP example:

#include <iostream>

template<class Derived>
struct Base {
    void f() { static_cast<Derived *>(this)->f(); }
    void g() { static_cast<Derived *>(this)->g(); }
};

struct Foo : public Base<Foo> {
    void f() { std::cout << 42 << std::endl; }
};

int main() {
    Foo foo;
    foo.f(); // just OK
    foo.g(); // this will stack overflow and segfault
}

If this was regular virtual inheritance I could have mark virtual f and g methods as pure like

struct Base {
    virtual void f() = 0;
    virtual void g() = 0;
};

and get a compile time error about Foo being abstract. But CRTP offers no such protection. Can I implement it somehow? Runtime check is acceptable too. I thought about comparing this->f pointer with static_cast<Derived *>(this)->f, but didn't manage to make it work.

uranix
  • 675
  • 5
  • 18

4 Answers4

25

You can assert at compile time that the two pointers to member functions are different, e.g.:

template<class Derived>
struct Base {
    void g() {
        static_assert(&Derived::g != &Base<Derived>::g,
                      "Derived classes must implement g()."); 
        static_cast<Derived *>(this)->g(); 
    }
};
Holt
  • 32,271
  • 6
  • 74
  • 116
  • 2
    You where uncertain if this was standard defined behaviour in a comment above. Care to expand on that in this answer? – Yakk - Adam Nevraumont Jul 18 '17 at 15:15
  • 1
    @Yakk I was not sure about the rule (conversion / comparison) for pointer to member functions between base / derived class. I checked the standard (in particular [conv.mem#2]) and some questions on SO and I am pretty confident that this is valid. But if you have reason to believe this is not, feel free to explain - If this is incorrect, I'll remove this answer quickly. – Holt Jul 18 '17 at 15:29
13

Here is another possibility:

#include <iostream>

template<class Derived>
struct Base {
    auto f() { return static_cast<Derived *>(this)->f(); }
    auto g() { return static_cast<Derived *>(this)->g(); }
};

struct Foo : public Base<Foo> {
    void f() { std::cout << 42 << std::endl; }
};

int main() {
    Foo foo;
    foo.f(); // just OK
    foo.g(); // this will not compile
}

For GCC, it gives a pretty clear error message ("error: use of 'auto Base::g() [with Derived = Foo]' before deduction of 'auto'"), while for Clang, it gives a slightly less readable infinitely recursing template instantiation of Base<Foo>::g, with g instantiating itself but eventually ending in an error.

River
  • 7,472
  • 11
  • 47
  • 61
Johannes Schaub - litb
  • 466,055
  • 116
  • 851
  • 1,175
12

You could use this solution, you can have pure "non-virtual abstract" function, and it maps as much as possible to CRTP this recommendation of H. Sutter:

template<class Derived>
struct Base
  {
  void f(){static_cast<Derived*>(this)->do_f();}
  void g(){static_cast<Derived*>(this)->do_g();}

  private:
  //Derived must implement do_f
  void do_f()=delete;
  //do_g as a default implementation
  void do_g(){}
  };

struct derived
  :Base<derived>
  {
  friend struct Base<derived>;

  private:
  void do_f(){}
  };
Oliv
  • 16,492
  • 1
  • 24
  • 63
0

You could consider doing something like this instead. You can make Derived a member and either supply it as a template parameter directly each time you instantiate a Base or else use a type alias as I have done in this example:

template<class Derived>
struct Base {
    void f() { d.f(); }
    void g() { d.g(); }
private:
    Derived d;
};

struct FooImpl {
    void f() { std::cout << 42 << std::endl; }
};

using Foo = Base<FooImpl>;

int main() {
    Foo foo;
    foo.f(); // OK
    foo.g(); // compile time error
}

Of course Derived is no longer derived so you might pick a better name for it.

Galik
  • 42,526
  • 3
  • 76
  • 100
  • 3
    The name isn't as bad as the changed semantics. One can't substitute `Derived` for `Base` anymore. So generic functions a-la `template void foo(Base&) { }` will not work anymore. – StoryTeller - Unslander Monica Jul 18 '17 at 12:25
  • @StoryTeller It doesn't pretend to be semantically identical. It is marked as a possible (safe) alternative which is good in many situations. (I use it all the time) – Galik Jul 18 '17 at 18:46