#include <iostream>
#include <memory>

template <typename T>
struct Node {
    T m_info;

    std::shared_ptr<Node<T>> m_next;
    std::shared_ptr<Node<T>> m_prev;
};

template <typename T>
struct List {

    void print() {
        auto n = m_head.get();

        while (n) {
            std::cout << n->m_info << '\n';

            n = n->m_next.get();
        }
    }

    void add(T value) {
        auto node = std::make_shared<Node<T>>(Node<T>{ value, m_head, nullptr});
        if (m_head) m_head.get()->m_prev = m_head;
        m_head = node;
    }

private:
    std::shared_ptr<Node<T>> m_head;
};

struct noisy_at_dying {
    int id;

    ~noisy_at_dying() { std::cout << id << " is dying\n"; }
};

// for print
std::ostream& operator<<(std::ostream& os, const noisy_at_dying& nad) {return os << nad.id;}

int main() {

    {
        List<noisy_at_dying> l;

        for (auto i = int{ 0 }; i < 3; ++i)
            l.add({ i });

        std::cout << "\nThe list is populated. Printing...\n\n";

        l.print();

        std::cout << "\nThe list is about to be destroyed...\n\n";
    }

    std::cout << "\nThe list has been destroyed.\n";
}