#pragma once #include #include #include #include #include #include namespace mbgl { namespace style { namespace conversion { /* The `conversion` namespace defines conversions from JSON structures conforming to the schema defined by the Mapbox Style Specification, to the various C++ types that form the C++ model of that domain: * `std::unique_ptr` * `std::unique_ptr` * `Filter` * `PropertyValue` A single template function serves as the public interface: template optional convert(const Convertible& input, Error& error); Where `T` is one of the above types. If the conversion fails, the result is empty, and the error parameter includes diagnostic text suitable for presentation to a library user. Otherwise, a filled optional is returned. `Convertible` is a type that encapsulates a special form of polymorphism over various underlying types that can serve as input to the conversion algorithm. For instance, on macOS, we need to support conversion from both RapidJSON types, and a JSON structure represented with `NSArray`/`NSDictionary`/etc. On Qt, we need to support conversion from RapidJSON types and QVariant. We don't want to use traditional forms of polymorphism to accomplish this: * Compile time polymorphism using a template parameter for the actual value type leads to excessive code bloat and long compile times. * Runtime polymorphism using virtual methods requires extra heap allocation and ubiquitous use of std::unique_ptr, unsuitable for this performance-sensitive code. Therefore, we're using a custom implementation of runtime polymorphism where we manually create and dispatch through a table of function pointers (vtable), while keeping the storage for any of the possible underlying types inline on the stack, using `std::aligned_storage`. For a given underlying type T, an explicit specialization of `ConversionTraits` must be provided. This specialization must provide the following static methods: * `isUndefined(v)` -- returns a boolean indication whether `v` is undefined or a JSON null * `isArray(v)` -- returns a boolean indicating whether `v` represents a JSON array * `arrayLength(v)` -- called only if `isArray(v)`; returns a size_t length * `arrayMember(v)` -- called only if `isArray(v)`; returns `V` or `V&` * `isObject(v)` -- returns a boolean indicating whether `v` represents a JSON object * `objectMember(v, name)` -- called only if `isObject(v)`; `name` is `const char *`; return value: * is true when evaluated in a boolean context iff the named member exists * is convertable to a `V` or `V&` when dereferenced * `eachMember(v, [] (const std::string&, const V&) -> optional {...})` -- called only if `isObject(v)`; calls the provided lambda once for each key and value of the object; short-circuits if any call returns an `Error` * `toBool(v)` -- returns `optional`, absence indicating `v` is not a JSON boolean * `toNumber(v)` -- returns `optional`, absence indicating `v` is not a JSON number * `toDouble(v)` -- returns `optional`, absence indicating `v` is not a JSON number * `toString(v)` -- returns `optional`, absence indicating `v` is not a JSON string * `toValue(v)` -- returns `optional`, a variant type, for generic conversion, absence indicating `v` is not a boolean, number, or string. Numbers should be converted to unsigned integer, signed integer, or floating point, in descending preference. In addition, the type T must be move-constructable. And finally, `Convertible::Storage`, a typedef for `std::aligned_storage_t`, must be large enough to satisfy the memory requirements for any of the possible underlying types. (A static assert will fail if this is not the case.) `Convertible` itself is movable, but not copyable. A moved-from `Convertible` is in an invalid state; you must not do anything with it except let it go out of scope. */ template class ConversionTraits; class Convertible { public: template Convertible(T&& value) : vtable(vtableForType>()) { static_assert(sizeof(Storage) >= sizeof(std::decay_t), "Storage must be large enough to hold value type"); new (static_cast(&storage)) std::decay_t(std::forward(value)); } Convertible(Convertible&& v) : vtable(v.vtable) { if (vtable) { vtable->move(std::move(v.storage), this->storage); } } ~Convertible() { if (vtable) { vtable->destroy(storage); } } Convertible& operator=(Convertible&& v) { if (vtable) { vtable->destroy(storage); } vtable = v.vtable; if (vtable) { vtable->move(std::move(v.storage), this->storage); } v.vtable = nullptr; return *this; } Convertible() = delete; Convertible(const Convertible&) = delete; Convertible& operator=(const Convertible&) = delete; friend inline bool isUndefined(const Convertible& v) { assert(v.vtable); return v.vtable->isUndefined(v.storage); } friend inline bool isArray(const Convertible& v) { assert(v.vtable); return v.vtable->isArray(v.storage); } friend inline std::size_t arrayLength(const Convertible& v) { assert(v.vtable); return v.vtable->arrayLength(v.storage); } friend inline Convertible arrayMember(const Convertible& v, std::size_t i) { assert(v.vtable); return v.vtable->arrayMember(v.storage, i); } friend inline bool isObject(const Convertible& v) { assert(v.vtable); return v.vtable->isObject(v.storage); } friend inline optional objectMember(const Convertible& v, const char * name) { assert(v.vtable); return v.vtable->objectMember(v.storage, name); } friend inline optional eachMember(const Convertible& v, const std::function (const std::string&, const Convertible&)>& fn) { assert(v.vtable); return v.vtable->eachMember(v.storage, fn); } friend inline optional toBool(const Convertible& v) { assert(v.vtable); return v.vtable->toBool(v.storage); } friend inline optional toNumber(const Convertible& v) { assert(v.vtable); return v.vtable->toNumber(v.storage); } friend inline optional toDouble(const Convertible& v) { assert(v.vtable); return v.vtable->toDouble(v.storage); } friend inline optional toString(const Convertible& v) { assert(v.vtable); return v.vtable->toString(v.storage); } friend inline optional toValue(const Convertible& v) { assert(v.vtable); return v.vtable->toValue(v.storage); } friend inline optional toGeoJSON(const Convertible& v, Error& error) { assert(v.vtable); return v.vtable->toGeoJSON(v.storage, error); } private: #if __ANDROID__ // Android: JSValue* or mbgl::android::Value using Storage = std::aligned_storage_t<32, 8>; #elif __QT__ // Qt: JSValue* or QVariant using Storage = std::aligned_storage_t<32, 8>; #else // Node: JSValue* or v8::Local // iOS/macOS: JSValue* or id using Storage = std::aligned_storage_t<8, 8>; #endif struct VTable { void (*move) (Storage&& src, Storage& dest); void (*destroy) (Storage&); bool (*isUndefined) (const Storage&); bool (*isArray) (const Storage&); std::size_t (*arrayLength) (const Storage&); Convertible (*arrayMember) (const Storage&, std::size_t); bool (*isObject) (const Storage&); optional (*objectMember) (const Storage&, const char *); optional (*eachMember) (const Storage&, const std::function (const std::string&, const Convertible&)>&); optional (*toBool) (const Storage&); optional (*toNumber) (const Storage&); optional (*toDouble) (const Storage&); optional (*toString) (const Storage&); optional (*toValue) (const Storage&); // https://github.com/mapbox/mapbox-gl-native/issues/5623 optional (*toGeoJSON) (const Storage&, Error&); }; // Extracted this function from the table below to work around a GCC bug with differing // visibility settings for capturing lambdas: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80947 template static auto vtableEachMember(const Storage& s, const std::function(const std::string&, const Convertible&)>& fn) { return ConversionTraits::eachMember(reinterpret_cast(s), [&](const std::string& k, T&& v) { return fn(k, Convertible(std::move(v))); }); } template static VTable* vtableForType() { using Traits = ConversionTraits; static VTable vtable = { [] (Storage&& src, Storage& dest) { auto srcValue = reinterpret_cast(src); new (static_cast(&dest)) T(std::move(srcValue)); srcValue.~T(); }, [] (Storage& s) { reinterpret_cast(s).~T(); }, [] (const Storage& s) { return Traits::isUndefined(reinterpret_cast(s)); }, [] (const Storage& s) { return Traits::isArray(reinterpret_cast(s)); }, [] (const Storage& s) { return Traits::arrayLength(reinterpret_cast(s)); }, [] (const Storage& s, std::size_t i) { return Convertible(Traits::arrayMember(reinterpret_cast(s), i)); }, [] (const Storage& s) { return Traits::isObject(reinterpret_cast(s)); }, [] (const Storage& s, const char * key) { optional member = Traits::objectMember(reinterpret_cast(s), key); if (member) { return optional(Convertible(std::move(*member))); } else { return optional(); } }, vtableEachMember, [] (const Storage& s) { return Traits::toBool(reinterpret_cast(s)); }, [] (const Storage& s) { return Traits::toNumber(reinterpret_cast(s)); }, [] (const Storage& s) { return Traits::toDouble(reinterpret_cast(s)); }, [] (const Storage& s) { return Traits::toString(reinterpret_cast(s)); }, [] (const Storage& s) { return Traits::toValue(reinterpret_cast(s)); }, [] (const Storage& s, Error& err) { return Traits::toGeoJSON(reinterpret_cast(s), err); } }; return &vtable; } VTable* vtable; Storage storage; }; template MBGL_NOINLINE optional convert(const Convertible& value, Error& error, Args&&...args) { return Converter()(value, error, std::forward(args)...); } } // namespace conversion } // namespace style } // namespace mbgl