diff --git a/src/cata_void.h b/src/cata_void.h new file mode 100644 index 0000000000000..2ee8d8d152329 --- /dev/null +++ b/src/cata_void.h @@ -0,0 +1,13 @@ +#ifndef CATA_SRC_CATA_VOID_H +#define CATA_SRC_CATA_VOID_H + +// For some template magic in generic_factory.h, it's much easier to use void_t +// However, C++14 does not have this, so we need our own. +// It's nothing complex, just strip it out when C++17 comes. +namespace cata +{ +template +using void_t = void; +} // namespace cata + +#endif // CATA_SRC_CATA_VOID_H diff --git a/src/generic_factory.h b/src/generic_factory.h index ad5497b99c6ec..bc19a954db89b 100644 --- a/src/generic_factory.h +++ b/src/generic_factory.h @@ -10,6 +10,7 @@ #include "assign.h" #include "catacharset.h" +#include "cata_void.h" #include "debug.h" #include "enum_bitset.h" #include "init.h" @@ -548,11 +549,176 @@ inline void mandatory( const JsonObject &jo, const bool was_loaded, const std::s } } +/* + * Template vodoo: + * The compiler will construct the appropriate one of these based on if the + * type can support the operations being done. + * So, it defaults to the false_type, but if it can use the *= operator + * against a float, it then supports proportional, and the handle_proportional + * template that isn't just a dummy is constructed. + * Similarly, if it can use a += operator against it's own type, the non-dummy + * handle_relative template is constructed. + */ +template> +struct supports_proportional : std::false_type { }; + +template +struct supports_proportional() *= std::declval() )>> : +std::true_type {}; + +template> +struct supports_relative : std::false_type { }; + +template +struct supports_relative < T, cata::void_t < decltype( std::declval() += std::declval() ) +>> : std::true_type {}; + +// Explicitly specialize these templates for a couple types +// So the compiler does not attempt to use a template that it should not +template<> +struct supports_proportional : std::false_type {}; + +template<> +struct supports_relative : std::false_type {}; + +template<> +struct supports_relative : std::false_type {}; + +// This checks that all units:: types will support relative and proportional +static_assert( supports_relative::value, "units should support relative" ); +static_assert( supports_proportional::value, "units should support proportional" ); + +static_assert( supports_relative::value, "ints should support relative" ); +static_assert( supports_proportional::value, "ints should support proportional" ); + +static_assert( !supports_relative::value, "bools should not support relative" ); +static_assert( !supports_proportional::value, "bools should not support proportional" ); + +// Using these with ints doesn't make sense in practice, but it doesn't matter here +// The type that it is templated with does not change it's behavior +static_assert( !supports_relative>::value, + "string ids should not support relative" ); +static_assert( !supports_proportional>::value, + "string ids should not support proportional" ); + +// Using these with ints doesn't make sense in practice, but it doesn't matter here +// The type that it is templated with does not change it's behavior +static_assert( !supports_relative>::value, + "int ids should not support relative" ); +static_assert( !supports_proportional>::value, + "int ids should not support proportional" ); + +static_assert( !supports_relative::value, "strings should not support relative" ); +static_assert( !supports_proportional::value, + "strings should not support proportional" ); + +// Grab an enum class from debug.h +static_assert( !supports_relative::value, "enum classes should not support relative" ); +static_assert( !supports_proportional::value, + "enum classes should not support proportional" ); + +// Grab a normal enum from there too +static_assert( !supports_relative::value, "enums should not support relative" ); +static_assert( !supports_proportional::value, "enums should not support relative" ); + +// Dummy template: +// Warn if it's trying to use proportional where it cannot, but otherwise just +// return. +template < typename MemberType, std::enable_if_t < !supports_proportional::value > * = + nullptr > +inline bool handle_proportional( const JsonObject &jo, const std::string &name, MemberType & ) +{ + if( jo.has_object( "proportional" ) ) { + JsonObject proportional = jo.get_object( "proportional" ); + proportional.allow_omitted_members(); + if( proportional.has_member( name ) ) { + debugmsg( "Member %s of type %s does not support proportional", name, typeid( MemberType ).name() ); + } + } + return false; +} + +// Real template: +// Copy-from makes it so the thing we're inheriting from is used to construct +// this, so member will contain the value of the thing we inherit from +// So, check if there is a proportional entry, check if it's got a valid value +// and if it does, multiply the member by it. +template::value>* = nullptr> +inline bool handle_proportional( const JsonObject &jo, const std::string &name, MemberType &member ) +{ + if( jo.has_object( "proportional" ) ) { + JsonObject proportional = jo.get_object( "proportional" ); + proportional.allow_omitted_members(); + // We need to check this here, otherwise we get problems with unvisited members + if( !proportional.has_member( name ) ) { + return false; + } + if( proportional.has_float( name ) ) { + double scalar = proportional.get_float( name ); + if( scalar <= 0 || scalar == 1 ) { + debugmsg( "Invalid scalar %g for %s", scalar, name ); + return false; + } + member *= scalar; + return true; + } else { + jo.throw_error( "Invalid scalar for %s", name ); + } + } + return false; +} + +// Dummy template: +// Warn when trying to use relative when it's not supported, but otherwise, +// return +template < typename MemberType, + std::enable_if_t < !supports_relative::value > * = nullptr + > +inline bool handle_relative( const JsonObject &jo, const std::string &name, MemberType & ) +{ + if( jo.has_object( "relative" ) ) { + JsonObject relative = jo.get_object( "relative" ); + relative.allow_omitted_members(); + if( !relative.has_member( name ) ) { + return false; + } + debugmsg( "Member %s of type %s does not support relative", name, typeid( MemberType ).name() ); + } + return false; +} + +// Real template: +// Copy-from makes it so the thing we're inheriting from is used to construct +// this, so member will contain the value of the thing we inherit from +// So, check if there is a relative entry, then add it to our member +template::value>* = nullptr> +inline bool handle_relative( const JsonObject &jo, const std::string &name, MemberType &member ) +{ + if( jo.has_object( "relative" ) ) { + JsonObject relative = jo.get_object( "relative" ); + relative.allow_omitted_members(); + // This needs to happen here, otherwise we get unvisited members + if( !relative.has_member( name ) ) { + return false; + } + MemberType adder; + if( relative.read( name, adder ) ) { + member += adder; + return true; + } else { + jo.throw_error( "Invalid adder for %s", name ); + } + } + return false; +} + +// No template magic here, yay! template inline void optional( const JsonObject &jo, const bool was_loaded, const std::string &name, MemberType &member ) { - if( !jo.read( name, member ) ) { + if( !jo.read( name, member ) && !handle_proportional( jo, name, member ) && + !handle_relative( jo, name, member ) ) { if( !was_loaded ) { member = MemberType(); } @@ -573,7 +739,8 @@ template::is_container, + int >::type = 0, + std::enable_if_t < !supports_relative::value > * = nullptr + > + bool do_relative( const JsonObject &jo, const std::string &name, C & ) const { + if( jo.has_object( "relative" ) ) { + JsonObject relative = jo.get_object( "relative" ); + relative.allow_omitted_members(); + if( !relative.has_member( name ) ) { + return false; + } + debugmsg( "Member %s of type %s does not support relative", name, typeid( C ).name() ); + } + return false; + } + template < typename C, typename std::enable_if < !reader_detail::handler::is_container, + int >::type = 0, std::enable_if_t::value> * = nullptr > + bool do_relative( const JsonObject &jo, const std::string &name, C &member ) const { + if( jo.has_object( "relative" ) ) { + JsonObject relative = jo.get_object( "relative" ); + relative.allow_omitted_members(); + const Derived &derived = static_cast( *this ); + // This needs to happen here, otherwise we get unvisited members + if( !relative.has_member( name ) ) { + return false; + } + C adder = derived.get_next( *relative.get_raw( name ) ); + member += adder; + return true; + } + return false; + } + + template + bool read_normal( const JsonObject &jo, const std::string &name, C &member ) const { + if( jo.has_member( name ) ) { + const Derived &derived = static_cast( *this ); + member = derived.get_next( *jo.get_raw( name ) ); + return true; + } + return false; + } + /** * Implements the reader interface, handles a simple data member. */ @@ -853,12 +1065,9 @@ class generic_typed_reader int >::type = 0 > bool operator()( const JsonObject &jo, const std::string &member_name, C &member, bool /*was_loaded*/ ) const { - const Derived &derived = static_cast( *this ); - if( !jo.has_member( member_name ) ) { - return false; - } - member = derived.get_next( *jo.get_raw( member_name ) ); - return true; + return !read_normal( jo, member_name, member ) && + !handle_proportional( jo, member_name, member ) && + !do_relative( jo, member_name, member ); } };