c++boostc++14boost-range

boost::adaptor::filtered core dumps with boost::range_detail::default_constructible_unary_fn_wrapper "Assertion `m_impl' failed"


I get an assertion failure inside boost::range_detail::default_constructible_unary_fn_wrapper when I run this code. The assertion appears to be checking that the functor has been initialized inside the filter object.

#include <boost/range/adaptor/filtered.hpp>

#include <iomanip>
#include <iostream>
#include <map>
#include <unordered_map>
#include <vector>

template<class RangeType>
static auto get_range_and_range_size(const RangeType& in_range, std::input_iterator_tag) -> auto {
    /*
     * we have an InputRange, but we need to know the size of the range.
     * so we eagerly copy the values into a vector so we can get the size of the range
     * and also return the range.
     * */
    auto out_range = std::vector<decltype(*std::begin(in_range))>(
        std::begin(in_range),
        std::end(in_range)
    );

    return std::make_tuple(std::move(out_range), out_range.size());
}

template<class RangeType>
static auto get_range_and_range_size(const RangeType& in_range, std::forward_iterator_tag) -> auto {
    return std::make_tuple(std::ref(in_range), boost::distance(in_range));
}

struct Cache {
    std::unordered_map<int, int> m_map;

    template<class RangeT>
    auto Insert(RangeT in_range) -> void {
        typename std::iterator_traits<
            decltype(std::begin(in_range))
        >::iterator_category iterator_category;

        std::cout << "No core dump yet!\n";

        auto range_and_range_size = get_range_and_range_size(
            boost::adaptors::filter(
                in_range,
                /*
                 * filter out any keys that are already in the cache and NOT TTLed yet
                 * */
                [this](const auto& key_value) -> bool {
                    const auto find_itr = m_map.find(key_value.first);

                    return (m_map.end() == find_itr)    /*  the key was not in the cache    */
                           || hasTTLed(find_itr->second);  /*  the key was in the cache but its value has TTLed  */
                }
            ),
            iterator_category
        );

        for(auto&& key_value : std::get<0>(range_and_range_size)) {
            m_map.emplace(key_value);
        }

        std::cout << "Can't reach this code. :(\n";
    }

    auto hasTTLed(int) const noexcept -> bool {
        /*
         *  TTL impl goes here
         */
        return false;
    }
};

auto main(int, char*[]) -> int {
    Cache a{};

    std::vector<std::pair<int, int>> v{
        {0, 0},
        {1, 1},
        {2, 2},
        {3, 3}
    };

    a.Insert(v);

    std::map<int, int> b{
        {0, 1},
        {1, 0},
        {2, 1},
        {3, 1}
    };

    a.Insert(b);
}

I get this with whatever version of boost that coliru uses, as well as 1.60.

Reproduced on coliru here.

Can you help me understand a) what is happening here, and b) how I fix it?


Solution

  • It was a lifetimes issue.

    The issue was in get_range_and_range_size, which was capturing a const RangeType& when it should have been capturing a RangeType&&. Returning a std::ref to a temporary captured that way was causing calls to uninitialized ranges in the code acting on range_and_range_size.

    These definitions of get_range_and_range_size solve the problem.

    template<class RangeType>
    static auto get_range_and_range_size(RangeType&& in_range, std::input_iterator_tag) -> auto {
        /*
         * we have an InputRange, but we need to know the size of the range.
         * so we eagerly copy the values into a vector so we can get the size of the range
         * and also return the range.
         * */
        auto out_range = std::vector<decltype(*std::begin(in_range))>(
            std::begin(in_range),
            std::end(in_range)
        );
    
        const auto out_size = out_range.size(); /*  capture the value before moving the container   */
        return std::make_tuple(std::move(out_range), out_size);
    }
    
    template<class RangeType>
    static auto get_range_and_range_size(RangeType&& in_range, std::forward_iterator_tag) -> auto {
        const auto size = boost::distance(in_range); /*  capture the value before moving the container   */
        return std::make_tuple(std::move(in_range), size);
    }