diff options
author | Alex Turbov <i.zaufi@gmail.com> | 2024-08-14 16:15:34 (GMT) |
---|---|---|
committer | Alex Turbov <i.zaufi@gmail.com> | 2024-08-26 15:01:48 (GMT) |
commit | 0c17cc9ef572c6d5b2de019e29f12cfeaec65aa7 (patch) | |
tree | 00bebd0062f297cc31d9cbfeda97d26141e3c402 /Source/cmJSONHelpers.h | |
parent | cd4210eb6d721a1b75e739e81250d1ac5a35b99f (diff) | |
download | CMake-0c17cc9ef572c6d5b2de019e29f12cfeaec65aa7.zip CMake-0c17cc9ef572c6d5b2de019e29f12cfeaec65aa7.tar.gz CMake-0c17cc9ef572c6d5b2de019e29f12cfeaec65aa7.tar.bz2 |
cmJSONHelpers.h: Add `FilteredObject` helper
Iterate over the object's members and call a filter callable to decide what
to do with the current key/value. A filter returns one of the `FilterResult`
values. A container type is an associative or a sequence container of pairs
(key, value).
Refactor `MapFilter()` and `Map()` to use `FilteredObject()`. Moreover,
for C++ >= 17 implementation is more optimized depending on the given filter
object type and capable of detecting and properly calling the filter callable
using 1 or 3 arguments, up to totally eliminate any checking (even dummy)
in the generated code.
Supported container types, used to append key/value items, aren't limited to
`std::map` only and can be any associative container or a sequenced one with
pairs of key/value as elements.
Diffstat (limited to 'Source/cmJSONHelpers.h')
-rw-r--r-- | Source/cmJSONHelpers.h | 130 |
1 files changed, 115 insertions, 15 deletions
diff --git a/Source/cmJSONHelpers.h b/Source/cmJSONHelpers.h index 0bb32f4..369c0b7 100644 --- a/Source/cmJSONHelpers.h +++ b/Source/cmJSONHelpers.h @@ -7,8 +7,10 @@ #include <algorithm> #include <functional> #include <iostream> +#include <iterator> #include <map> #include <string> +#include <type_traits> #include <utility> #include <vector> @@ -60,9 +62,28 @@ ErrorGenerator INVALID_NAMED_OBJECT_KEY( ObjectError errorType, const Json::Value::Members& extraFields); } -struct cmJSONHelperBuilder +#if __cplusplus >= 201703L +namespace details { +// A meta-function to check if a given callable type +// can be called with the only string ref arg. +template <typename F, typename Enable = void> +struct is_bool_filter +{ + static constexpr bool value = false; +}; + +template <typename F> +struct is_bool_filter<F, + std::enable_if_t<std::is_same_v< + std::invoke_result_t<F, const std::string&>, bool>>> { + static constexpr bool value = true; +}; +} +#endif +struct cmJSONHelperBuilder +{ template <typename T> class Object { @@ -323,13 +344,28 @@ struct cmJSONHelperBuilder [](const T&) { return true; }); } - template <typename T, typename F, typename Filter> - static cmJSONHelper<std::map<std::string, T>> MapFilter( + enum class FilterResult + { + Continue, ///< A filter has accepted a given key (and value) + Skip, ///< A filter has rejected a given key (or value) + Error ///< A filter has found and reported an error + }; + + /// Iterate over the object's members and call a filter callable to + /// decide what to do with the current key/value. + /// A filter returns one of the `FilterResult` values. + /// A container type is an associative or a sequence + /// container of pairs (key, value). + template <typename Container, typename F, typename Filter> + static cmJSONHelper<Container> FilteredObject( const JsonErrors::ErrorGenerator& error, F func, Filter filter) { - return [error, func, filter](std::map<std::string, T>& out, - const Json::Value* value, + return [error, func, filter](Container& out, const Json::Value* value, cmJSONState* state) -> bool { + // NOTE Some compile-time code path don't use `filter` at all. + // So, suppress "unused lambda capture" warning is needed. + static_cast<void>(filter); + if (!value) { out.clear(); return true; @@ -339,30 +375,94 @@ struct cmJSONHelperBuilder return false; } out.clear(); + auto outIt = std::inserter(out, out.end()); bool success = true; for (auto const& key : value->getMemberNames()) { state->push_stack(key, &(*value)[key]); - if (!filter(key)) { - state->pop_stack(); - continue; - } - T t; - if (!func(t, &(*value)[key], state)) { - success = false; +#if __cplusplus >= 201703L + if constexpr (std::is_same_v<Filter, std::true_type>) { + // Filtering functionality isn't needed at all... + } else if constexpr (details::is_bool_filter<Filter>::value) { + // A given `Filter` is `bool(const std::string&)` callable. + if (!filter(key)) { + state->pop_stack(); + continue; + } + } else { +#endif + // A full-featured `Filter` has been given + auto res = filter(key, &(*value)[key], state); + if (res == FilterResult::Skip) { + state->pop_stack(); + continue; + } + if (res == FilterResult::Error) { + state->pop_stack(); + success = false; + break; + } +#if __cplusplus >= 201703L } - out.emplace(key, std::move(t)); +#endif + typename Container::value_type::second_type t; + // ATTENTION Call the function first (for it's side-effects), + // then accumulate the result! + success = func(t, &(*value)[key], state) && success; + outIt = typename Container::value_type{ key, std::move(t) }; state->pop_stack(); } return success; }; } + template <typename T, typename F, typename Filter> + static cmJSONHelper<std::map<std::string, T>> MapFilter( + const JsonErrors::ErrorGenerator& error, F func, Filter filter) + { + // clang-format off + return FilteredObject<std::map<std::string, T>>( + error, func, +#if __cplusplus >= 201703L + // In C++ 17 a filter callable can be passed as is. + // Depending on its type `FilteredObject()` will call + // it with a key only (backward compatible behavior) + // or with 3 args supported by the full-featured + // filtering feature. + filter +#else + // For C++14 and below, to keep backward compatibility + // with CMake Presets code, `MapFilter()` can accept only + // `bool(const std::string&)` callables. + [filter](const std::string &key, const Json::Value * /*value*/, + cmJSONState * /*state*/) -> FilterResult { + // Simple adaptor to translate `bool` to `FilterResult` + return filter(key) ? FilterResult::Continue : FilterResult::Skip; + } +#endif + ); + // clang-format on + } + template <typename T, typename F> static cmJSONHelper<std::map<std::string, T>> Map( const JsonErrors::ErrorGenerator& error, F func) { - return MapFilter<T, F>(error, func, - [](const std::string&) { return true; }); + // clang-format off + return FilteredObject<std::map<std::string, T>>( + error, func, +#if __cplusplus >= 201703L + // With C++ 17 and above, pass a marker type, that no + // filtering is needed at all. + std::true_type() +#else + // In C++ 14 and below, pass an always-true dummy functor. + [](const std::string& /*key*/, const Json::Value* /*value*/, + cmJSONState* /*state*/) -> FilterResult { + return FilterResult::Continue; + } +#endif + ); + // clang-format on } template <typename T, typename F> |