Skip to content

Correct class names for KeysView, ValuesView and ItemsView in bind_map #4353

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
db09d94
Create templated abstract classes KeysView, ValuesView and ItemsView,…
Nov 22, 2022
6bbde12
We don't want to wrap different ValuesView objects for double values …
Nov 22, 2022
f405e11
Fallback to C++ names if key or values types are not wrapped
Nov 22, 2022
72a645e
Added a test for .keys(), .values() and .items() returning the same t…
Nov 22, 2022
3e1b653
Fixed wrong use of auto in a declarator list: the two descriptions mi…
Nov 23, 2022
c2846b4
Fixes for clang-tidy issues: explicit single-argument constructor, us…
Nov 23, 2022
dfc3ee1
Bugfix for old versions of clang++, which seem to have trouble with t…
Nov 23, 2022
42d8694
Bugfix for clang++, which doesn't have some of the names in runtime u…
Nov 23, 2022
c558073
A fix for clang-tidy performance-inefficient-string-concatenation iss…
Nov 23, 2022
f0294f1
Possible fix for clang++ linking issues - make the descriptions stati…
Nov 23, 2022
ebf874d
Correct names for previously-wrapped types as keys/values of maps
Nov 26, 2022
24019ae
Bugfix - typo in type info names which caused things to segfault
Nov 26, 2022
ddcc4e2
Apply suggestions from code review
aimir Nov 26, 2022
21ec3ec
Use detail::remove_cvref_t instead of doing remove_cv and remove_refe…
Nov 28, 2022
8abc0ae
Avoid names with double underscore, as they are reserved
Dec 2, 2022
60d754f
Improved testing for KeysView, ValuesView and ItemsView: check type n…
Dec 4, 2022
e054752
Moved description logic to helper function in type_caster_base.h
Dec 4, 2022
b4041b8
style: pre-commit fixes
pre-commit-ci[bot] Dec 4, 2022
3a3b9f2
Fix a clang-tidy issue: do not use 'else' after 'return'
Dec 4, 2022
4525c5d
Apply suggestion by @Skylion007, with additional trivial simplification.
rwgk Dec 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions include/pybind11/detail/type_caster_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -1006,5 +1006,14 @@ class type_caster_base : public type_caster_generic {
static Constructor make_move_constructor(...) { return nullptr; }
};

PYBIND11_NOINLINE std::string type_info_description(const std::type_info &ti) {
if (auto *type_data = get_type_info(ti)) {
handle th((PyObject *) type_data->type);
return th.attr("__module__").cast<std::string>() + '.'
+ th.attr("__qualname__").cast<std::string>();
}
return clean_type_id(ti.name());
}

PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
154 changes: 107 additions & 47 deletions include/pybind11/stl_bind.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@
#pragma once

#include "detail/common.h"
#include "detail/type_caster_base.h"
#include "cast.h"
#include "operators.h"

#include <algorithm>
#include <sstream>
#include <type_traits>

PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
Expand Down Expand Up @@ -636,18 +639,52 @@ auto map_if_insertion_operator(Class_ &cl, std::string const &name)
"Return the canonical string representation of this map.");
}

template <typename Map>
template <typename KeyType>
struct keys_view {
Map &map;
virtual size_t len() = 0;
virtual iterator iter() = 0;
virtual bool contains(const KeyType &k) = 0;
virtual bool contains(const object &k) = 0;
virtual ~keys_view() = default;
};

template <typename Map>
template <typename MappedType>
struct values_view {
Map &map;
virtual size_t len() = 0;
virtual iterator iter() = 0;
virtual ~values_view() = default;
};

template <typename Map>
template <typename KeyType, typename MappedType>
struct items_view {
virtual size_t len() = 0;
virtual iterator iter() = 0;
virtual ~items_view() = default;
};

template <typename Map, typename KeysView>
struct KeysViewImpl : public KeysView {
explicit KeysViewImpl(Map &map) : map(map) {}
size_t len() override { return map.size(); }
iterator iter() override { return make_key_iterator(map.begin(), map.end()); }
bool contains(const typename Map::key_type &k) override { return map.find(k) != map.end(); }
bool contains(const object &) override { return false; }
Map &map;
};

template <typename Map, typename ValuesView>
struct ValuesViewImpl : public ValuesView {
explicit ValuesViewImpl(Map &map) : map(map) {}
size_t len() override { return map.size(); }
iterator iter() override { return make_value_iterator(map.begin(), map.end()); }
Map &map;
};

template <typename Map, typename ItemsView>
struct ItemsViewImpl : public ItemsView {
explicit ItemsViewImpl(Map &map) : map(map) {}
size_t len() override { return map.size(); }
iterator iter() override { return make_iterator(map.begin(), map.end()); }
Map &map;
};

Expand All @@ -657,9 +694,11 @@ template <typename Map, typename holder_type = std::unique_ptr<Map>, typename...
class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&...args) {
using KeyType = typename Map::key_type;
using MappedType = typename Map::mapped_type;
using KeysView = detail::keys_view<Map>;
using ValuesView = detail::values_view<Map>;
using ItemsView = detail::items_view<Map>;
using StrippedKeyType = detail::remove_cvref_t<KeyType>;
using StrippedMappedType = detail::remove_cvref_t<MappedType>;
using KeysView = detail::keys_view<StrippedKeyType>;
using ValuesView = detail::values_view<StrippedMappedType>;
using ItemsView = detail::items_view<StrippedKeyType, StrippedMappedType>;
using Class_ = class_<Map, holder_type>;

// If either type is a non-module-local bound type then make the map binding non-local as well;
Expand All @@ -673,12 +712,57 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&
}

Class_ cl(scope, name.c_str(), pybind11::module_local(local), std::forward<Args>(args)...);
class_<KeysView> keys_view(
scope, ("KeysView[" + name + "]").c_str(), pybind11::module_local(local));
class_<ValuesView> values_view(
scope, ("ValuesView[" + name + "]").c_str(), pybind11::module_local(local));
class_<ItemsView> items_view(
scope, ("ItemsView[" + name + "]").c_str(), pybind11::module_local(local));
static constexpr auto key_type_descr = detail::make_caster<KeyType>::name;
static constexpr auto mapped_type_descr = detail::make_caster<MappedType>::name;
std::string key_type_name(key_type_descr.text), mapped_type_name(mapped_type_descr.text);

// If key type isn't properly wrapped, fall back to C++ names
if (key_type_name == "%") {
key_type_name = detail::type_info_description(typeid(KeyType));
}
// Similarly for value type:
if (mapped_type_name == "%") {
mapped_type_name = detail::type_info_description(typeid(MappedType));
}

// Wrap KeysView[KeyType] if it wasn't already wrapped
if (!detail::get_type_info(typeid(KeysView))) {
class_<KeysView> keys_view(
scope, ("KeysView[" + key_type_name + "]").c_str(), pybind11::module_local(local));
keys_view.def("__len__", &KeysView::len);
keys_view.def("__iter__",
&KeysView::iter,
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);
keys_view.def("__contains__",
static_cast<bool (KeysView::*)(const KeyType &)>(&KeysView::contains));
// Fallback for when the object is not of the key type
keys_view.def("__contains__",
static_cast<bool (KeysView::*)(const object &)>(&KeysView::contains));
}
// Similarly for ValuesView:
if (!detail::get_type_info(typeid(ValuesView))) {
class_<ValuesView> values_view(scope,
("ValuesView[" + mapped_type_name + "]").c_str(),
pybind11::module_local(local));
values_view.def("__len__", &ValuesView::len);
values_view.def("__iter__",
&ValuesView::iter,
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);
}
// Similarly for ItemsView:
if (!detail::get_type_info(typeid(ItemsView))) {
class_<ItemsView> items_view(
scope,
("ItemsView[" + key_type_name + ", ").append(mapped_type_name + "]").c_str(),
pybind11::module_local(local));
items_view.def("__len__", &ItemsView::len);
items_view.def("__iter__",
&ItemsView::iter,
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);
}

cl.def(init<>());

Expand All @@ -698,19 +782,25 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&

cl.def(
"keys",
[](Map &m) { return KeysView{m}; },
[](Map &m) {
return std::unique_ptr<KeysView>(new detail::KeysViewImpl<Map, KeysView>(m));
},
keep_alive<0, 1>() /* Essential: keep map alive while view exists */
);

cl.def(
"values",
[](Map &m) { return ValuesView{m}; },
[](Map &m) {
return std::unique_ptr<ValuesView>(new detail::ValuesViewImpl<Map, ValuesView>(m));
},
keep_alive<0, 1>() /* Essential: keep map alive while view exists */
);

cl.def(
"items",
[](Map &m) { return ItemsView{m}; },
[](Map &m) {
return std::unique_ptr<ItemsView>(new detail::ItemsViewImpl<Map, ItemsView>(m));
},
keep_alive<0, 1>() /* Essential: keep map alive while view exists */
);

Expand Down Expand Up @@ -749,36 +839,6 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&

cl.def("__len__", &Map::size);

keys_view.def("__len__", [](KeysView &view) { return view.map.size(); });
keys_view.def(
"__iter__",
[](KeysView &view) { return make_key_iterator(view.map.begin(), view.map.end()); },
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);
keys_view.def("__contains__", [](KeysView &view, const KeyType &k) -> bool {
auto it = view.map.find(k);
if (it == view.map.end()) {
return false;
}
return true;
});
// Fallback for when the object is not of the key type
keys_view.def("__contains__", [](KeysView &, const object &) -> bool { return false; });

values_view.def("__len__", [](ValuesView &view) { return view.map.size(); });
values_view.def(
"__iter__",
[](ValuesView &view) { return make_value_iterator(view.map.begin(), view.map.end()); },
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);

items_view.def("__len__", [](ItemsView &view) { return view.map.size(); });
items_view.def(
"__iter__",
[](ItemsView &view) { return make_iterator(view.map.begin(), view.map.end()); },
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);

return cl;
}

Expand Down
26 changes: 26 additions & 0 deletions tests/test_stl_binders.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,29 @@ def test_map_delitem():
del um["ua"]
assert sorted(list(um)) == ["ub"]
assert sorted(list(um.items())) == [("ub", 2.6)]


def test_map_view_types():
map_string_double = m.MapStringDouble()
unordered_map_string_double = m.UnorderedMapStringDouble()
map_string_double_const = m.MapStringDoubleConst()
unordered_map_string_double_const = m.UnorderedMapStringDoubleConst()

assert map_string_double.keys().__class__.__name__ == "KeysView[str]"
assert map_string_double.values().__class__.__name__ == "ValuesView[float]"
assert map_string_double.items().__class__.__name__ == "ItemsView[str, float]"

keys_type = type(map_string_double.keys())
assert type(unordered_map_string_double.keys()) is keys_type
assert type(map_string_double_const.keys()) is keys_type
assert type(unordered_map_string_double_const.keys()) is keys_type

values_type = type(map_string_double.values())
assert type(unordered_map_string_double.values()) is values_type
assert type(map_string_double_const.values()) is values_type
assert type(unordered_map_string_double_const.values()) is values_type

items_type = type(map_string_double.items())
assert type(unordered_map_string_double.items()) is items_type
assert type(map_string_double_const.items()) is items_type
assert type(unordered_map_string_double_const.items()) is items_type