c++crtp

Why is a struct field looking at uninitialized memory when using CRTP?


I started experimenting with CRTP and ran into a very interesting problem that has been keeping me awake for two days now.

Using CRTP I declare 3 structures: Column, Header and Footer, which are inherited from Widget.

The Column structure takes the other widgets in the constructor and stores them in std::tuple, then in the Render() method, calls the Render() method of the widgets that are stored in std::tuple.

In main() you can see that the Header widget's title field is set, but during debugging the Render() method of the Column widget I noticed that the title field looks at uninitialized memory, so the program ends up with a segfault.

The code uses standard c++20.

Who can suggest what the problem is? Thanks in advance!

#include <iostream>
#include <tuple>
#include <string>

template<typename T>
struct Widget {
    void Render() {
        static_cast<T*>(this)->Render();
    }
};

template<typename ...Args>
struct Column : public Widget<Column<Args...>> {
    Column(Widget<Args>... args)
        : children(std::forward<Widget<Args>>(args)...)
    {}

    void Render() {
        std::cout << "Column {" << std::endl;

        std::apply([](auto&... widgets){
            auto render = [](auto& widget){
                widget.Render();
            };

            (render(widgets), ...);
        }, children);

        std::cout << "}" << std::endl;
    }

private:
    std::tuple<Widget<Args>...> children;
};

struct Header : public Widget<Header> {
    void Render() {
        std::cout << "Header { title=" << this->title << " }" << std::endl;
    }

    std::string title;
};

struct Footer : public Widget<Footer> {
    void Render() {
        std::cout << "Footer {}" << std::endl;
    }
};

template<typename T>
void AplicationRun(Widget<T> widget) {
    widget.Render();
}

int main() {
    auto app = Column{
        Header{
            .title = "Title"
        },
        Column{},
        Footer{},
    };

    AplicationRun(app);

    return 0;
}

I tried using move sematic in the Column constructor but that didn't help.


Solution

  • The argument to the function

    template<typename T>
    void AplicationRun(Widget<T> widget) {
    

    is a Widget<Column> which is not a Column. Unlike other languages you might be used to, this argument is not a reference to the object, but is in fact a whole object. To fit the Column object into this Widget<Column> argument the compiler only copies the Widget<Column> part, a common pitfall called slicing because the argument is only a "slice" of the original object.

    You could fix this by changing Widget<T> to T. However it would still make a copy of the Column object, which I suppose you don't want.

    To avoid making a copy you can pass by reference: Widget<T> & or T &. Either one is okay, because the Widget<T> & parameter can be a reference to a Column object.


    If passing by reference, you can also choose to use Widget<T> const & or T const & which prevents the function from modifying the object (and the Render function will need to be marked const so you can still call it). You don't need that in this small program if you don't want to, but it's common for programmers on bigger programs to restrict themselves like this because it helps to avoid mistakes (bugs).