Skip to content

Commit 9db9880

Browse files
aimirAmiraimirSkylion007pre-commit-ci[bot]
authored
Correct class names for KeysView, ValuesView and ItemsView in bind_map (#4353)
* Create templated abstract classes KeysView, ValuesView and ItemsView, and implement them on-the-fly when wrapping any specific map type * We don't want to wrap different ValuesView objects for double values and const double, for example, as both wrappers will be named ValuesView[float] * Fallback to C++ names if key or values types are not wrapped * Added a test for .keys(), .values() and .items() returning the same types for similarly-typed maps * Fixed wrong use of auto in a declarator list: the two descriptions might have different types * Fixes for clang-tidy issues: explicit single-argument constructor, using the 'override' keyword when overriding functions * Bugfix for old versions of clang++, which seem to have trouble with the struct being defined inside a module, which was also needlessly ugly anyway * Bugfix for clang++, which doesn't have some of the names in runtime uness they are specified to be static * A fix for clang-tidy performance-inefficient-string-concatenation issues - I personally think this looks uglier, but it's probably worth it for clang-tidy to be happy * Possible fix for clang++ linking issues - make the descriptions static constexpr to make sure they are known before linking * Correct names for previously-wrapped types as keys/values of maps * Bugfix - typo in type info names which caused things to segfault * Apply suggestions from code review Co-authored-by: Aaron Gokaslan <[email protected]> * Use detail::remove_cvref_t instead of doing remove_cv and remove_reference separately * Avoid names with double underscore, as they are reserved * Improved testing for KeysView, ValuesView and ItemsView: check type names + stricter asserts * Moved description logic to helper function in type_caster_base.h * style: pre-commit fixes * Fix a clang-tidy issue: do not use 'else' after 'return' * Apply suggestion by @Skylion007, with additional trivial simplification. Co-authored-by: Amir <aimir@local> Co-authored-by: aimir <aimir@localhost> Co-authored-by: Aaron Gokaslan <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ralf W. Grosse-Kunstleve <[email protected]>
1 parent 0012685 commit 9db9880

File tree

3 files changed

+142
-47
lines changed

3 files changed

+142
-47
lines changed

include/pybind11/detail/type_caster_base.h

+9
Original file line numberDiff line numberDiff line change
@@ -1006,5 +1006,14 @@ class type_caster_base : public type_caster_generic {
10061006
static Constructor make_move_constructor(...) { return nullptr; }
10071007
};
10081008

1009+
PYBIND11_NOINLINE std::string type_info_description(const std::type_info &ti) {
1010+
if (auto *type_data = get_type_info(ti)) {
1011+
handle th((PyObject *) type_data->type);
1012+
return th.attr("__module__").cast<std::string>() + '.'
1013+
+ th.attr("__qualname__").cast<std::string>();
1014+
}
1015+
return clean_type_id(ti.name());
1016+
}
1017+
10091018
PYBIND11_NAMESPACE_END(detail)
10101019
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

include/pybind11/stl_bind.h

+107-47
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@
1010
#pragma once
1111

1212
#include "detail/common.h"
13+
#include "detail/type_caster_base.h"
14+
#include "cast.h"
1315
#include "operators.h"
1416

1517
#include <algorithm>
1618
#include <sstream>
19+
#include <type_traits>
1720

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

639-
template <typename Map>
642+
template <typename KeyType>
640643
struct keys_view {
641-
Map &map;
644+
virtual size_t len() = 0;
645+
virtual iterator iter() = 0;
646+
virtual bool contains(const KeyType &k) = 0;
647+
virtual bool contains(const object &k) = 0;
648+
virtual ~keys_view() = default;
642649
};
643650

644-
template <typename Map>
651+
template <typename MappedType>
645652
struct values_view {
646-
Map &map;
653+
virtual size_t len() = 0;
654+
virtual iterator iter() = 0;
655+
virtual ~values_view() = default;
647656
};
648657

649-
template <typename Map>
658+
template <typename KeyType, typename MappedType>
650659
struct items_view {
660+
virtual size_t len() = 0;
661+
virtual iterator iter() = 0;
662+
virtual ~items_view() = default;
663+
};
664+
665+
template <typename Map, typename KeysView>
666+
struct KeysViewImpl : public KeysView {
667+
explicit KeysViewImpl(Map &map) : map(map) {}
668+
size_t len() override { return map.size(); }
669+
iterator iter() override { return make_key_iterator(map.begin(), map.end()); }
670+
bool contains(const typename Map::key_type &k) override { return map.find(k) != map.end(); }
671+
bool contains(const object &) override { return false; }
672+
Map &map;
673+
};
674+
675+
template <typename Map, typename ValuesView>
676+
struct ValuesViewImpl : public ValuesView {
677+
explicit ValuesViewImpl(Map &map) : map(map) {}
678+
size_t len() override { return map.size(); }
679+
iterator iter() override { return make_value_iterator(map.begin(), map.end()); }
680+
Map &map;
681+
};
682+
683+
template <typename Map, typename ItemsView>
684+
struct ItemsViewImpl : public ItemsView {
685+
explicit ItemsViewImpl(Map &map) : map(map) {}
686+
size_t len() override { return map.size(); }
687+
iterator iter() override { return make_iterator(map.begin(), map.end()); }
651688
Map &map;
652689
};
653690

@@ -657,9 +694,11 @@ template <typename Map, typename holder_type = std::unique_ptr<Map>, typename...
657694
class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&...args) {
658695
using KeyType = typename Map::key_type;
659696
using MappedType = typename Map::mapped_type;
660-
using KeysView = detail::keys_view<Map>;
661-
using ValuesView = detail::values_view<Map>;
662-
using ItemsView = detail::items_view<Map>;
697+
using StrippedKeyType = detail::remove_cvref_t<KeyType>;
698+
using StrippedMappedType = detail::remove_cvref_t<MappedType>;
699+
using KeysView = detail::keys_view<StrippedKeyType>;
700+
using ValuesView = detail::values_view<StrippedMappedType>;
701+
using ItemsView = detail::items_view<StrippedKeyType, StrippedMappedType>;
663702
using Class_ = class_<Map, holder_type>;
664703

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

675714
Class_ cl(scope, name.c_str(), pybind11::module_local(local), std::forward<Args>(args)...);
676-
class_<KeysView> keys_view(
677-
scope, ("KeysView[" + name + "]").c_str(), pybind11::module_local(local));
678-
class_<ValuesView> values_view(
679-
scope, ("ValuesView[" + name + "]").c_str(), pybind11::module_local(local));
680-
class_<ItemsView> items_view(
681-
scope, ("ItemsView[" + name + "]").c_str(), pybind11::module_local(local));
715+
static constexpr auto key_type_descr = detail::make_caster<KeyType>::name;
716+
static constexpr auto mapped_type_descr = detail::make_caster<MappedType>::name;
717+
std::string key_type_name(key_type_descr.text), mapped_type_name(mapped_type_descr.text);
718+
719+
// If key type isn't properly wrapped, fall back to C++ names
720+
if (key_type_name == "%") {
721+
key_type_name = detail::type_info_description(typeid(KeyType));
722+
}
723+
// Similarly for value type:
724+
if (mapped_type_name == "%") {
725+
mapped_type_name = detail::type_info_description(typeid(MappedType));
726+
}
727+
728+
// Wrap KeysView[KeyType] if it wasn't already wrapped
729+
if (!detail::get_type_info(typeid(KeysView))) {
730+
class_<KeysView> keys_view(
731+
scope, ("KeysView[" + key_type_name + "]").c_str(), pybind11::module_local(local));
732+
keys_view.def("__len__", &KeysView::len);
733+
keys_view.def("__iter__",
734+
&KeysView::iter,
735+
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
736+
);
737+
keys_view.def("__contains__",
738+
static_cast<bool (KeysView::*)(const KeyType &)>(&KeysView::contains));
739+
// Fallback for when the object is not of the key type
740+
keys_view.def("__contains__",
741+
static_cast<bool (KeysView::*)(const object &)>(&KeysView::contains));
742+
}
743+
// Similarly for ValuesView:
744+
if (!detail::get_type_info(typeid(ValuesView))) {
745+
class_<ValuesView> values_view(scope,
746+
("ValuesView[" + mapped_type_name + "]").c_str(),
747+
pybind11::module_local(local));
748+
values_view.def("__len__", &ValuesView::len);
749+
values_view.def("__iter__",
750+
&ValuesView::iter,
751+
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
752+
);
753+
}
754+
// Similarly for ItemsView:
755+
if (!detail::get_type_info(typeid(ItemsView))) {
756+
class_<ItemsView> items_view(
757+
scope,
758+
("ItemsView[" + key_type_name + ", ").append(mapped_type_name + "]").c_str(),
759+
pybind11::module_local(local));
760+
items_view.def("__len__", &ItemsView::len);
761+
items_view.def("__iter__",
762+
&ItemsView::iter,
763+
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
764+
);
765+
}
682766

683767
cl.def(init<>());
684768

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

699783
cl.def(
700784
"keys",
701-
[](Map &m) { return KeysView{m}; },
785+
[](Map &m) {
786+
return std::unique_ptr<KeysView>(new detail::KeysViewImpl<Map, KeysView>(m));
787+
},
702788
keep_alive<0, 1>() /* Essential: keep map alive while view exists */
703789
);
704790

705791
cl.def(
706792
"values",
707-
[](Map &m) { return ValuesView{m}; },
793+
[](Map &m) {
794+
return std::unique_ptr<ValuesView>(new detail::ValuesViewImpl<Map, ValuesView>(m));
795+
},
708796
keep_alive<0, 1>() /* Essential: keep map alive while view exists */
709797
);
710798

711799
cl.def(
712800
"items",
713-
[](Map &m) { return ItemsView{m}; },
801+
[](Map &m) {
802+
return std::unique_ptr<ItemsView>(new detail::ItemsViewImpl<Map, ItemsView>(m));
803+
},
714804
keep_alive<0, 1>() /* Essential: keep map alive while view exists */
715805
);
716806

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

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

752-
keys_view.def("__len__", [](KeysView &view) { return view.map.size(); });
753-
keys_view.def(
754-
"__iter__",
755-
[](KeysView &view) { return make_key_iterator(view.map.begin(), view.map.end()); },
756-
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
757-
);
758-
keys_view.def("__contains__", [](KeysView &view, const KeyType &k) -> bool {
759-
auto it = view.map.find(k);
760-
if (it == view.map.end()) {
761-
return false;
762-
}
763-
return true;
764-
});
765-
// Fallback for when the object is not of the key type
766-
keys_view.def("__contains__", [](KeysView &, const object &) -> bool { return false; });
767-
768-
values_view.def("__len__", [](ValuesView &view) { return view.map.size(); });
769-
values_view.def(
770-
"__iter__",
771-
[](ValuesView &view) { return make_value_iterator(view.map.begin(), view.map.end()); },
772-
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
773-
);
774-
775-
items_view.def("__len__", [](ItemsView &view) { return view.map.size(); });
776-
items_view.def(
777-
"__iter__",
778-
[](ItemsView &view) { return make_iterator(view.map.begin(), view.map.end()); },
779-
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
780-
);
781-
782842
return cl;
783843
}
784844

tests/test_stl_binders.py

+26
Original file line numberDiff line numberDiff line change
@@ -309,3 +309,29 @@ def test_map_delitem():
309309
del um["ua"]
310310
assert sorted(list(um)) == ["ub"]
311311
assert sorted(list(um.items())) == [("ub", 2.6)]
312+
313+
314+
def test_map_view_types():
315+
map_string_double = m.MapStringDouble()
316+
unordered_map_string_double = m.UnorderedMapStringDouble()
317+
map_string_double_const = m.MapStringDoubleConst()
318+
unordered_map_string_double_const = m.UnorderedMapStringDoubleConst()
319+
320+
assert map_string_double.keys().__class__.__name__ == "KeysView[str]"
321+
assert map_string_double.values().__class__.__name__ == "ValuesView[float]"
322+
assert map_string_double.items().__class__.__name__ == "ItemsView[str, float]"
323+
324+
keys_type = type(map_string_double.keys())
325+
assert type(unordered_map_string_double.keys()) is keys_type
326+
assert type(map_string_double_const.keys()) is keys_type
327+
assert type(unordered_map_string_double_const.keys()) is keys_type
328+
329+
values_type = type(map_string_double.values())
330+
assert type(unordered_map_string_double.values()) is values_type
331+
assert type(map_string_double_const.values()) is values_type
332+
assert type(unordered_map_string_double_const.values()) is values_type
333+
334+
items_type = type(map_string_double.items())
335+
assert type(unordered_map_string_double.items()) is items_type
336+
assert type(map_string_double_const.items()) is items_type
337+
assert type(unordered_map_string_double_const.items()) is items_type

0 commit comments

Comments
 (0)