Boost Python exposes some STL iterator wrappers in the "stl_iterator" header that let you go from begin to end like a normal C++ iteration:
https://www.boost.org/doc/libs/1_75_0/libs/python/doc/html/reference/high_level_components/boost_python_stl_iterator_hpp.html
For Python version 2 you can use d.items() or d.iteritems() depending on whether you want to iterate lazily or not. For Python version 3 has a slight problem - you'd expect items to be a lazy dict view, but instead Boost Python converts it to a list. Therefore, I've called .attr("items")() instead to bypass the in-build conversion thus getting a lazy wrapper. I've returned this object to Python just to confirm that it's the view rather than a list.
Calling stl_input_iterator<tuple>() gives you an iterator for a (Python) tuple object from which you can extract the key and the value.
#include <boost/python/dict.hpp>
#include <boost/python/tuple.hpp>
#include <boost/python/stl_iterator.hpp>
#include <boost/python/extract.hpp>
#include <boost/python/str.hpp>
#include <boost/python.hpp>
#include <iostream>
using namespace boost::python;
object print_dict_to_cout(dict d) {
    auto items = d.attr("items")(); // just plain d.items or d.iteritems for Python 2!
    for (auto it = stl_input_iterator<tuple>(items); it != stl_input_iterator<tuple>(); ++it) {
        tuple kv = *it;
        auto key = kv[0];
        auto value = kv[1];
        std::cout << extract<const char*>(str(key)) << " : " << extract<const char*>(str(value)) << std::endl;
    }
    return items;
}
BOOST_PYTHON_MODULE(iterdict)
{
    def("print_dict_to_cout", print_dict_to_cout);
}
The advantage of doing it this way is that it's lazy, and doesn't create the intermediate list.