Protect CRTP pattern from stack overflowing in "pure virtual" calls
Asked Answered
A

4

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.

Ardelia answered 18/7, 2017 at 9:50 Comment(4)
I don't know if this is a standard-defined behavior, but you can static_assert inside Base::g that &Derived::g != &Base<Derived>::g.Hautesavoie
This appears to work aswell: coliru.stacked-crooked.com/a/80021ad0bfb7aa47Catechetical
@JohannesSchaub-litb That is clever! You should write up an answer.Luci
@Yakk done, thanks. Clang's error message is suboptimal, though.Catechetical
C
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.

Catechetical answered 18/7, 2017 at 15:30 Comment(5)
That is actually rare, that clang gives a worse error message than gcc, in my experience.Luci
Latest version of clang seems to produce something close to gcc (clang >= 3.9.1).Hautesavoie
The error message might be less than perfect, but you can't argue with the simplicity of the solution!Farmhand
Nice one, but is there possibility to adapt it for C++11?Ardelia
@Ardelia a similar approach can be applied, but then both the error message and the code become much uglier: coliru.stacked-crooked.com/a/f7b767fcc6ce94d6Catechetical
H
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(); 
    }
};
Hautesavoie answered 18/7, 2017 at 10:13 Comment(2)
You where uncertain if this was standard defined behaviour in a comment above. Care to expand on that in this answer?Luci
@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.Hautesavoie
P
13

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(){}
  };
Persecution answered 18/7, 2017 at 11:29 Comment(0)
C
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.

Catechetical answered 18/7, 2017 at 15:30 Comment(5)
That is actually rare, that clang gives a worse error message than gcc, in my experience.Luci
Latest version of clang seems to produce something close to gcc (clang >= 3.9.1).Hautesavoie
The error message might be less than perfect, but you can't argue with the simplicity of the solution!Farmhand
Nice one, but is there possibility to adapt it for C++11?Ardelia
@Ardelia a similar approach can be applied, but then both the error message and the code become much uglier: coliru.stacked-crooked.com/a/f7b767fcc6ce94d6Catechetical
D
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.

Dowel answered 18/7, 2017 at 10:17 Comment(5)
The name isn't as bad as the changed semantics. One can't substitute Derived for Base<Derived> anymore. So generic functions a-la template<typename T> void foo(Base<T>&) { } will not work anymore.Colfin
@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)Dowel
This works, but simply isn't CRTP, which is about inheritance. Instead, this answer uses composition.Dahna
@Dahna My answer doesn't pretend to be CRTP. It is offered as an alternative solution.Dowel
RIght, you clearly said "instead" :-) ... nevertheless this could be missed in reading over it.Dahna

© 2022 - 2024 — McMap. All rights reserved.