diff --git a/README.md b/README.md index 84f6d28..03d602d 100644 --- a/README.md +++ b/README.md @@ -457,6 +457,148 @@ int i = 0; toml::from_toml(i, data.at("something")); ``` +### Conversion between toml value and your class + +You can also use `toml::get` and other related functions with the types you defined +after you implement some stuff. + +```cpp +namespace ext +{ +struct foo +{ + int a; + double b; + std::string c; +}; +} // ext + +const auto data = toml::parse("example.toml"); + +const foo f = toml::get(data.at("foo")); +``` + +There are 2 ways to use `toml::get` with the types that you defined. + +The first one is to implement `from_toml(const toml::value&)` member function. + +```cpp +namespace ext +{ +struct foo +{ + int a; + double b; + std::string c; + + void from_toml(const toml::value& v) + { + this->a = toml::find(v, "a"); + this->b = toml::find(v, "b"); + this->c = toml::find(v, "c"); + return; + } +}; +} // ext +``` + +In this way, because `toml::get` first constructs `foo` without arguments, +the type should be default-constructible. + +The second is to implement specialization of `toml::from` for your type. + +```cpp +namespace ext +{ +struct foo +{ + int a; + double b; + std::string c; +}; +} // ext + +namespace toml +{ +template<> +struct from +{ + ext::foo from_toml(const toml::value& v) + { + ext::foo f; + f.a = toml::find(v, "a"); + f.b = toml::find(v, "b"); + f.c = toml::find(v, "c"); + return f; + } +}; +} // toml +``` + +In this way, since the conversion function is introduced from out of the class, +you can add conversion between `toml::value` and classes defined in another library. + +Note that you cannot implement both of the functions because the overload +resolution of `toml::get` become ambiguous. + +---- + +The opposite direction is also supported in a similar way. You can directly +pass your type to `toml::value`'s constructor by introducing `into_iter` or +`toml::into`. + +```cpp +namespace ext +{ +struct foo +{ + int a; + double b; + std::string c; + + toml::table into_toml() const // you need to mark it const. + { + return toml::table{{"a", this->a}, {"b", this->b}, {"c", this->c}}; + } +}; +} // ext + +ext::foo f{42, 3.14, "foobar"}; +toml::value v(f); +``` + +The definition of `toml::into` is similar to `from_toml()`. + +```cpp +namespace ext +{ +struct foo +{ + int a; + double b; + std::string c; +}; +} // ext + +namespace toml +{ +template<> +struct into +{ + toml::table into_toml(const ext::foo& v) + { + return toml::table{{"a", this->a}, {"b", this->b}, {"c", this->c}}; + } +}; +} // toml + +ext::foo f{42, 3.14, "foobar"}; +toml::value v(f); +``` + +Any type that can be converted to `toml::value`, e.g. `toml::table`, `toml::array`, +is okay to return from `into_toml`. + ### visiting toml::value TOML v2.1.0+ provides `toml::visit` to apply a function to `toml::value` in the diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8db2420..cb3c790 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -29,6 +29,7 @@ set(TEST_NAMES test_parse_unicode test_error_detection test_format_error + test_extended_conversions ) CHECK_CXX_COMPILER_FLAG("-Wall" COMPILER_SUPPORTS_WALL) diff --git a/tests/test_extended_conversions.cpp b/tests/test_extended_conversions.cpp new file mode 100644 index 0000000..fd5d88d --- /dev/null +++ b/tests/test_extended_conversions.cpp @@ -0,0 +1,116 @@ +#define BOOST_TEST_MODULE "test_extended_conversions" +#ifdef UNITTEST_FRAMEWORK_LIBRARY_EXIST +#include +#else +#define BOOST_TEST_NO_LIB +#include +#endif +#include + +namespace extlib +{ +struct foo +{ + int a; + std::string b; +}; +struct bar +{ + int a; + std::string b; + + void from_toml(const toml::value& v) + { + this->a = toml::find(v, "a"); + this->b = toml::find(v, "b"); + return ; + } + + toml::table into_toml() const + { + return toml::table{{"a", this->a}, {"b", this->b}}; + } +}; +} // extlib + +namespace toml +{ +template<> +struct from +{ + static extlib::foo from_toml(const toml::value& v) + { + return extlib::foo{toml::find(v, "a"), toml::find(v, "b")}; + } +}; + +template<> +struct into +{ + static toml::table into_toml(const extlib::foo& f) + { + return toml::table{{"a", f.a}, {"b", f.b}}; + } +}; +} // toml + + +BOOST_AUTO_TEST_CASE(test_conversion_by_member_methods) +{ + const toml::value v{{"a", 42}, {"b", "baz"}}; + + const auto foo = toml::get(v); + BOOST_CHECK_EQUAL(foo.a, 42); + BOOST_CHECK_EQUAL(foo.b, "baz"); + + const toml::value v2(foo); + + BOOST_CHECK_EQUAL(v, v2); +} + +BOOST_AUTO_TEST_CASE(test_conversion_by_specialization) +{ + const toml::value v{{"a", 42}, {"b", "baz"}}; + + const auto bar = toml::get(v); + BOOST_CHECK_EQUAL(bar.a, 42); + BOOST_CHECK_EQUAL(bar.b, "baz"); + + const toml::value v2(bar); + + BOOST_CHECK_EQUAL(v, v2); +} + +BOOST_AUTO_TEST_CASE(test_recursive_conversion) +{ + const toml::value v{ + toml::table{{"a", 42}, {"b", "baz"}}, + toml::table{{"a", 43}, {"b", "qux"}}, + toml::table{{"a", 44}, {"b", "quux"}}, + toml::table{{"a", 45}, {"b", "foobar"}}, + }; + + const auto foos = toml::get>(v); + BOOST_CHECK_EQUAL(foos.size() , 4ul); + BOOST_CHECK_EQUAL(foos.at(0).a , 42); + BOOST_CHECK_EQUAL(foos.at(1).a , 43); + BOOST_CHECK_EQUAL(foos.at(2).a , 44); + BOOST_CHECK_EQUAL(foos.at(3).a , 45); + + BOOST_CHECK_EQUAL(foos.at(0).b , "baz"); + BOOST_CHECK_EQUAL(foos.at(1).b , "qux"); + BOOST_CHECK_EQUAL(foos.at(2).b , "quux"); + BOOST_CHECK_EQUAL(foos.at(3).b , "foobar"); + + const auto bars = toml::get>(v); + BOOST_CHECK_EQUAL(bars.size() , 4ul); + BOOST_CHECK_EQUAL(bars.at(0).a , 42); + BOOST_CHECK_EQUAL(bars.at(1).a , 43); + BOOST_CHECK_EQUAL(bars.at(2).a , 44); + BOOST_CHECK_EQUAL(bars.at(3).a , 45); + + BOOST_CHECK_EQUAL(bars.at(0).b , "baz"); + BOOST_CHECK_EQUAL(bars.at(1).b , "qux"); + BOOST_CHECK_EQUAL(bars.at(2).b , "quux"); + BOOST_CHECK_EQUAL(bars.at(3).b , "foobar"); +} diff --git a/toml/from.hpp b/toml/from.hpp new file mode 100644 index 0000000..8251973 --- /dev/null +++ b/toml/from.hpp @@ -0,0 +1,20 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_FROM_HPP +#define TOML11_FROM_HPP +#include "traits.hpp" + +namespace toml +{ + +template +struct from; +// { +// static T from_toml(const toml::value& v) +// { +// // User-defined conversions ... +// } +// }; + +} // toml +#endif // TOML11_FROM_HPP diff --git a/toml/get.hpp b/toml/get.hpp index 94b58f9..6aad019 100644 --- a/toml/get.hpp +++ b/toml/get.hpp @@ -2,6 +2,7 @@ // Distributed under the MIT License. #ifndef TOML11_GET_HPP #define TOML11_GET_HPP +#include "from.hpp" #include "result.hpp" #include "value.hpp" #include @@ -173,6 +174,20 @@ template::value, std::nullptr_t>::type = nullptr> T get(const toml::value& v); +template>, // not a toml::value + detail::has_from_toml_method, // but has from_toml(toml::value) memfn + std::is_default_constructible // and default constructible + >::value, std::nullptr_t>::type = nullptr> +T get(const toml::value& v); + +template> // not a toml::value + >::value, std::nullptr_t>::type = nullptr, + std::size_t = sizeof(::toml::from) // and has from specialization + > +T get(const toml::value& v); + // ============================================================================ // array-like types; most likely STL container, like std::vector, etc. @@ -297,6 +312,29 @@ T get(const toml::value& v) return map; } + +// ============================================================================ +// user-defined, but compatible types. + +template>, // not a toml::value + detail::has_from_toml_method, // but has from_toml(toml::value) memfn + std::is_default_constructible // and default constructible + >::value, std::nullptr_t>::type> +T get(const toml::value& v) +{ + T ud; + ud.from_toml(v); + return ud; +} +template> // not a toml::value + >::value, std::nullptr_t>::type, std::size_t> // and has from +T get(const toml::value& v) +{ + return ::toml::from::from_toml(v); +} + // ============================================================================ // find and get diff --git a/toml/into.hpp b/toml/into.hpp new file mode 100644 index 0000000..17f2248 --- /dev/null +++ b/toml/into.hpp @@ -0,0 +1,20 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_INTO_HPP +#define TOML11_INTO_HPP +#include "traits.hpp" + +namespace toml +{ + +template +struct into; +// { +// static toml::value into_toml(const T& user_defined_type) +// { +// // User-defined conversions ... +// } +// }; + +} // toml +#endif // TOML11_INTO_HPP diff --git a/toml/traits.hpp b/toml/traits.hpp index bf4917b..7f8f413 100644 --- a/toml/traits.hpp +++ b/toml/traits.hpp @@ -9,6 +9,9 @@ namespace toml { + +class value; // forward decl + namespace detail { @@ -45,6 +48,22 @@ struct has_resize_method_impl template static std::false_type check(...); }; +struct has_from_toml_method_impl +{ + template + static std::true_type check( + decltype(std::declval().from_toml(std::declval<::toml::value>()))*); + template + static std::false_type check(...); +}; +struct has_into_toml_method_impl +{ + template + static std::true_type check(decltype(std::declval().into_toml())*); + template + static std::false_type check(...); +}; + /// Intel C++ compiler can not use decltype in parent class declaration, here /// is a hack to work around it. https://stackoverflow.com/a/23953090/4692076 #ifdef __INTEL_COMPILER @@ -62,6 +81,14 @@ struct has_mapped_type : decltype(has_mapped_type_impl::check(nullptr)){}; template struct has_resize_method : decltype(has_resize_method_impl::check(nullptr)){}; + +template +struct has_from_toml_method +: decltype(has_from_toml_method_impl::check(nullptr)){}; +template +struct has_into_toml_method +: decltype(has_into_toml_method_impl::check(nullptr)){}; + #ifdef __INTEL_COMPILER #undef decltype(...) #endif diff --git a/toml/value.hpp b/toml/value.hpp index dc88898..739f676 100644 --- a/toml/value.hpp +++ b/toml/value.hpp @@ -3,6 +3,7 @@ #ifndef TOML11_VALUE_HPP #define TOML11_VALUE_HPP #include "traits.hpp" +#include "into.hpp" #include "utility.hpp" #include "exception.hpp" #include "storage.hpp" @@ -533,6 +534,46 @@ class value return *this; } + // user-defined ========================================================= + + // convert using into_toml() method ------------------------------------- + + template>, // not a toml::value + detail::has_into_toml_method // but has `into_toml` method + >::value, std::nullptr_t>::type = nullptr> + value(const T& ud): value(ud.into_toml()) {} + + template>, // not a toml::value + detail::has_into_toml_method // but has `into_toml` method + >::value, std::nullptr_t>::type = nullptr> + value& operator=(const T& ud) + { + *this = ud.into_toml(); + return *this; + } + + // convert using into struct ----------------------------------------- + + template>::value, + std::nullptr_t>::type = nullptr, + std::size_t S = sizeof(::toml::into)> + value(const T& ud): value(::toml::into::into_toml(ud)) {} + + template>::value, + std::nullptr_t>::type = nullptr, + std::size_t S = sizeof(::toml::into)> + value& operator=(const T& ud) + { + *this = ::toml::into::into_toml(ud); + return *this; + } + + // type checking and casting ============================================ + template bool is() const noexcept {return value_traits::type_index == this->type_;} bool is(value_t t) const noexcept {return t == this->type_;}