0

I am experimenting with CRTP and mixing it with interfaces, and I cannot explain why this program crashes (in Clang, GCC and MSVC). In latest Clang and GCC it builds without warning with -Wall, -Wextra. My guess is that the virtual method call is not resolved but I cannot explain why (no crash if GetInt() is removed from the interface). In my debugger I see that the crash happens on line static_cast<T*>(this)->GetInt().

#include <iostream>

class INode
{
public:
    virtual int GetInt() = 0;
protected:
    ~INode() {}
};

template<template<class> typename NodeBase>
class Node : public INode, public NodeBase<Node<NodeBase>>
{
public:
    int GetInt() override { return 42; }
};

template<typename T>
class MyNodeBase
{
public:
    int CallGetInt() {
        return static_cast<T*>(this)->GetInt();
    }
};

template<template<class> typename NodeBase>
int Foo1(Node<NodeBase> n) {
    return n.CallGetInt();
}

template<typename T>
int Foo2(MyNodeBase<T> n) {
    return n.CallGetInt();
}

int main() {
    Node<MyNodeBase> n;
    std::cout << Foo1(n) << std::endl; // ok
    std::cout << Foo2(n) << std::endl; // crash
}
Gabriel Devillers
  • 3,155
  • 2
  • 30
  • 53

2 Answers2

2

You're slicing n in your call to Foo2.

Foo2 accepts its parameter by value. That means that a copy of n's MyNodeBase<Node<MyNodeBase>> sub-object is what gets passed to Foo2. Since n in Foo2 is not a Node<MyNodeBase>, calling GetInt through the pointer returned from the cast in CallGetInt results in your program exhibiting undefined behavior.

If you change Foo2 to accept its parameter by reference, you program's behavior would be well-defined.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
1

Foo2 argument is passed by value. So the complete object n is of type MyNodeBase<Node<MyNodeBase>>. It is not a derived of Node<MyNodeBase>. But the expression static_cast<T*>(this) assumes that this is derived of Node<MyNodeBase> thus static_cast<T*>(this)->GetInt() is undefined behavior.

You avoid this error:

template<typename T>
class MyNodeBase
{
protected:
   MyNodeBase() = default;
   MyNodeBase(const MyNodeBase&) = default;
   MyNodeBase& operator = (const MyNodeBase&) = default;

public:
    
    int CallGetInt() {
        return static_cast<T*>(this)->GetInt();
    }
};
Oliv
  • 17,610
  • 1
  • 29
  • 72