Skip to content

Commit

Permalink
Fix compilation of input_adapter(container) in edge cases
Browse files Browse the repository at this point in the history
This fixes a compilation issue with the library if trying to use containers that
don't have non-member `begin()` and `end()` functions via ADL.

This patch extends the `using std::begin` and `using std::end` declarations to
also cover the return type deduction of the input_adapter() template
specialization for containers. The previous implementation only enabled the
detection of `std::begin()` and `std::end()` in the function body, making the
specialization unusable for container types that only have member `begin()` and
`end()` functions.

It is not typical to have `using` declarations in the namespace scope in a
header file. But a C++11 implementation can't rely on fully automatic return
type deduction, and needs to rely on ADL enabled helper templates. To prevent
the using declarations leaking, they are enclosed in another nested namespace.
  • Loading branch information
jasujm committed Dec 28, 2020
1 parent dfedefb commit 467f622
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 13 deletions.
31 changes: 26 additions & 5 deletions include/nlohmann/detail/input/input_adapters.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -371,14 +371,35 @@ typename iterator_input_adapter_factory<IteratorType>::adapter_type input_adapte
}

// Convenience shorthand from container to iterator
// Enables ADL on begin(container) and end(container)
// Encloses the using declarations in namespace for not to leak them to outside scope

namespace container_input_adapter_factory_impl {

using std::begin;
using std::end;

template<typename ContainerType, typename Enable = void>
struct container_input_adapter_factory {};

template<typename ContainerType>
auto input_adapter(const ContainerType& container) -> decltype(input_adapter(begin(container), end(container)))
struct container_input_adapter_factory< ContainerType,
void_t<decltype(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>()))> >
{
// Enable ADL
using std::begin;
using std::end;
using adapter_type = decltype(input_adapter(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>())));

static adapter_type create(const ContainerType& container)
{
return input_adapter(begin(container), end(container));
}
};

}

return input_adapter(begin(container), end(container));
template<typename ContainerType>
typename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type input_adapter(const ContainerType& container)
{
return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(container);
}

// Special cases with fast paths
Expand Down
35 changes: 28 additions & 7 deletions single_include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5183,14 +5183,35 @@ typename iterator_input_adapter_factory<IteratorType>::adapter_type input_adapte
}

// Convenience shorthand from container to iterator
// Enables ADL on begin(container) and end(container)
// Encloses the using declarations in namespace for not to leak them to outside scope

namespace container_input_adapter_factory_impl {

using std::begin;
using std::end;

template<typename ContainerType, typename Enable = void>
struct container_input_adapter_factory {};

template<typename ContainerType>
auto input_adapter(const ContainerType& container) -> decltype(input_adapter(begin(container), end(container)))
struct container_input_adapter_factory< ContainerType,
void_t<decltype(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>()))> >
{
// Enable ADL
using std::begin;
using std::end;
using adapter_type = decltype(input_adapter(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>())));

static adapter_type create(const ContainerType& container)
{
return input_adapter(begin(container), end(container));
}
};

return input_adapter(begin(container), end(container));
}

template<typename ContainerType>
typename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type input_adapter(const ContainerType& container)
{
return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(container);
}

// Special cases with fast paths
Expand Down Expand Up @@ -16801,7 +16822,7 @@ class basic_json
detail::parser_callback_t<basic_json>cb = nullptr,
const bool allow_exceptions = true,
const bool ignore_comments = false
)
)
{
return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter),
std::move(cb), allow_exceptions, ignore_comments);
Expand Down Expand Up @@ -25346,7 +25367,7 @@ template<>
inline void swap<nlohmann::json>(nlohmann::json& j1, nlohmann::json& j2) noexcept(
is_nothrow_move_constructible<nlohmann::json>::value&&
is_nothrow_move_assignable<nlohmann::json>::value
)
)
{
j1.swap(j2);
}
Expand Down
27 changes: 26 additions & 1 deletion test/src/unit-user_defined_input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const char* end(const MyContainer& c)
return c.data + strlen(c.data);
}

TEST_CASE("Custom container")
TEST_CASE("Custom container non-member begin/end")
{

MyContainer data{"[1,2,3,4]"};
Expand All @@ -75,6 +75,31 @@ TEST_CASE("Custom container")

}

TEST_CASE("Custom container member begin/end")
{
struct MyContainer2
{
const char* data;

const char* begin() const
{
return data;
}

const char* end() const
{
return data + strlen(data);
}
};

MyContainer2 data{"[1,2,3,4]"};
json as_json = json::parse(data);
CHECK(as_json.at(0) == 1);
CHECK(as_json.at(1) == 2);
CHECK(as_json.at(2) == 3);
CHECK(as_json.at(3) == 4);
}

TEST_CASE("Custom iterator")
{
const char* raw_data = "[1,2,3,4]";
Expand Down

0 comments on commit 467f622

Please sign in to comment.