mirror of
https://github.com/nlohmann/json.git
synced 2026-09-02 22:47:14 +00:00
Build the no-key_compare object type by composition, not inheritance
The "object type without key_compare" test failed on AppVeyor's MSVC 2017 jobs (/std:c++17): its no_key_compare_map derived publicly from std::map and shadowed the inherited key_compare type with a same-named member function, relying on ordinary member hiding to make key_compare unreachable as a type for the library's detection trait. MSVC 2017 does not honor that hiding for a typename-qualified lookup performed from outside the class and still resolves key_compare to the base's comparator type, so object_comparator_t incorrectly picked it up instead of falling back to default_object_comparator_t. Wrapping a std::map by composition instead removes the base class entirely, so there is no key_compare to find under any lookup rule, on any compiler. Also drops the now-unneeded JSON_BISECT_CUSTOM_CONTAINER_TESTS guard left over from narrowing this down: the void_erase_map test in the same file was never the cause and is re-enabled unconditionally. Verified locally with clang++ and g++ under C++17 and C++20. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -29,31 +29,85 @@ namespace
|
|||||||
// is still an incomplete type, and whether a hash map can be instantiated
|
// is still an incomplete type, and whether a hash map can be instantiated
|
||||||
// with an incomplete mapped type depends on the standard library (libstdc++ 9
|
// with an incomplete mapped type depends on the standard library (libstdc++ 9
|
||||||
// needs the size of the mapped type for its node type and rejects it). So the
|
// needs the size of the mapped type for its node type and rejects it). So the
|
||||||
// object type is built from std::map, and the inherited key_compare member
|
// object type wraps a std::map instead of inheriting from it: an earlier
|
||||||
// type is shadowed by an entity that is not a type -- the library's probe
|
// version derived from std::map and shadowed the inherited key_compare type
|
||||||
// then finds no type, exactly as for a hash map.
|
// with a same-named member function, relying on ordinary member hiding to
|
||||||
|
// make key_compare unreachable as a type. MSVC 2017 (AppVeyor, /std:c++17)
|
||||||
|
// does not honor that hiding for a typename-qualified lookup performed from
|
||||||
|
// outside the class and still resolves key_compare to the base's comparator
|
||||||
|
// type, so the library's probe incorrectly found one. Composition sidesteps
|
||||||
|
// the question entirely: with no base class, there is no key_compare to find
|
||||||
|
// under any lookup rule.
|
||||||
template<class Key, class T, class Compare, class Allocator>
|
template<class Key, class T, class Compare, class Allocator>
|
||||||
struct no_key_compare_map : std::map<Key, T, Compare, Allocator>
|
class no_key_compare_map
|
||||||
{
|
{
|
||||||
using base_t = std::map<Key, T, Compare, Allocator>;
|
using map_t = std::map<Key, T, Compare, Allocator>;
|
||||||
|
map_t data;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using key_type = typename map_t::key_type;
|
||||||
|
using mapped_type = typename map_t::mapped_type;
|
||||||
|
using value_type = typename map_t::value_type;
|
||||||
|
using size_type = typename map_t::size_type;
|
||||||
|
using allocator_type = typename map_t::allocator_type;
|
||||||
|
using iterator = typename map_t::iterator;
|
||||||
|
using const_iterator = typename map_t::const_iterator;
|
||||||
|
|
||||||
no_key_compare_map() = default;
|
no_key_compare_map() = default;
|
||||||
|
|
||||||
// converting between two basic_json types builds the object from a range
|
// converting between two basic_json types builds the object from a range
|
||||||
template<class InputIt>
|
template<class InputIt>
|
||||||
no_key_compare_map(InputIt first, InputIt last) : base_t(first, last) {}
|
no_key_compare_map(InputIt first, InputIt last) : data(first, last) {}
|
||||||
|
|
||||||
// shadows base_t::key_compare, which is a type; never defined or called
|
iterator begin() { return data.begin(); }
|
||||||
void key_compare();
|
iterator end() { return data.end(); }
|
||||||
|
const_iterator begin() const { return data.begin(); }
|
||||||
|
const_iterator end() const { return data.end(); }
|
||||||
|
const_iterator cbegin() const { return data.cbegin(); }
|
||||||
|
const_iterator cend() const { return data.cend(); }
|
||||||
|
|
||||||
|
bool empty() const { return data.empty(); }
|
||||||
|
size_type size() const { return data.size(); }
|
||||||
|
size_type max_size() const { return data.max_size(); }
|
||||||
|
void clear() { data.clear(); }
|
||||||
|
|
||||||
|
iterator find(const key_type& key) { return data.find(key); }
|
||||||
|
const_iterator find(const key_type& key) const { return data.find(key); }
|
||||||
|
size_type count(const key_type& key) const { return data.count(key); }
|
||||||
|
|
||||||
|
std::pair<iterator, bool> emplace(const key_type& key, const mapped_type& value)
|
||||||
|
{
|
||||||
|
return data.emplace(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<iterator, bool> insert(const value_type& value) { return data.insert(value); }
|
||||||
|
|
||||||
|
template<class InputIt>
|
||||||
|
void insert(InputIt first, InputIt last) { data.insert(first, last); }
|
||||||
|
|
||||||
|
mapped_type& operator[](const key_type& key) { return data[key]; }
|
||||||
|
|
||||||
|
mapped_type& at(const key_type& key) { return data.at(key); }
|
||||||
|
const mapped_type& at(const key_type& key) const { return data.at(key); }
|
||||||
|
|
||||||
|
iterator erase(iterator pos) { return data.erase(pos); }
|
||||||
|
iterator erase(iterator first, iterator last) { return data.erase(first, last); }
|
||||||
|
size_type erase(const key_type& key) { return data.erase(key); }
|
||||||
|
|
||||||
|
void swap(no_key_compare_map& other) { data.swap(other.data); }
|
||||||
|
|
||||||
|
friend bool operator==(const no_key_compare_map& lhs, const no_key_compare_map& rhs)
|
||||||
|
{
|
||||||
|
return lhs.data == rhs.data;
|
||||||
|
}
|
||||||
|
friend bool operator<(const no_key_compare_map& lhs, const no_key_compare_map& rhs)
|
||||||
|
{
|
||||||
|
return lhs.data < rhs.data;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using no_key_compare_json = nlohmann::basic_json<no_key_compare_map>;
|
using no_key_compare_json = nlohmann::basic_json<no_key_compare_map>;
|
||||||
|
|
||||||
// TEMPORARY: guarded out to split this translation unit's two object types
|
|
||||||
// while narrowing down an AppVeyor failure on MSVC 2015/2017 whose build log
|
|
||||||
// this environment cannot reach. The macro is deliberately never defined.
|
|
||||||
#ifdef JSON_BISECT_CUSTOM_CONTAINER_TESTS
|
|
||||||
|
|
||||||
// An ObjectType whose erase(iterator) returns void rather than the following
|
// An ObjectType whose erase(iterator) returns void rather than the following
|
||||||
// iterator, as for instance Abseil's hash maps do
|
// iterator, as for instance Abseil's hash maps do
|
||||||
template<class Key, class T, class Compare, class Allocator>
|
template<class Key, class T, class Compare, class Allocator>
|
||||||
@@ -71,11 +125,8 @@ struct void_erase_map : std::map<Key, T, Compare, Allocator>
|
|||||||
|
|
||||||
using void_erase_json = nlohmann::basic_json<void_erase_map>;
|
using void_erase_json = nlohmann::basic_json<void_erase_map>;
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
#ifdef JSON_BISECT_CUSTOM_CONTAINER_TESTS
|
|
||||||
TEST_CASE("object type whose erase() returns void")
|
TEST_CASE("object type whose erase() returns void")
|
||||||
{
|
{
|
||||||
SECTION("erasing every element through the returned iterator")
|
SECTION("erasing every element through the returned iterator")
|
||||||
@@ -125,7 +176,6 @@ TEST_CASE("object type whose erase() returns void")
|
|||||||
CHECK(j.empty());
|
CHECK(j.empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST_CASE("object type without key_compare")
|
TEST_CASE("object type without key_compare")
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user