From df6dcbc4ed04dd5d46fb91c2c73eb9ba51f561e1 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Sat, 16 Mar 2019 14:16:31 +0900 Subject: [PATCH 1/8] feat: check a class has from/into_toml member fn to support better serialization --- toml/traits.hpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/toml/traits.hpp b/toml/traits.hpp index 79bb8b2..1991728 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 From 6929bcdf78899fac8c5fc900b7d1b4c563776d4d Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Sat, 16 Mar 2019 14:27:05 +0900 Subject: [PATCH 2/8] feat: add from and into --- toml/from.hpp | 20 ++++++++++++++++++++ toml/into.hpp | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 toml/from.hpp create mode 100644 toml/into.hpp 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/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 From b1b72a94a8656609284c006b1687217284689a27 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Sat, 16 Mar 2019 14:44:04 +0900 Subject: [PATCH 3/8] feat: support conversion with external types --- toml/get.hpp | 26 ++++++++++++++++++++++++++ toml/value.hpp | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/toml/get.hpp b/toml/get.hpp index 80336ce..dc1b495 100644 --- a/toml/get.hpp +++ b/toml/get.hpp @@ -2,6 +2,7 @@ // Distributed under the MIT License. #ifndef TOML11_GET #define TOML11_GET +#include "from.hpp" #include "result.hpp" #include "value.hpp" #include @@ -297,6 +298,31 @@ 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 = nullptr> +T get(const toml::value& v) +{ + T ud; + ud.from_toml(v); + return ud; +} +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) +{ + return ::toml::from::from_toml(v); +} + // ============================================================================ // find and get diff --git a/toml/value.hpp b/toml/value.hpp index a866b04..00b790a 100644 --- a/toml/value.hpp +++ b/toml/value.hpp @@ -3,6 +3,7 @@ #ifndef TOML11_VALUE #define TOML11_VALUE #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_;} From 31e450f9aff2267094085322167f921522383be5 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Sat, 16 Mar 2019 15:46:21 +0900 Subject: [PATCH 4/8] test: add test for `from/into` based conversions --- tests/CMakeLists.txt | 1 + tests/test_extended_conversions.cpp | 82 +++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 tests/test_extended_conversions.cpp 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..dd20758 --- /dev/null +++ b/tests/test_extended_conversions.cpp @@ -0,0 +1,82 @@ +#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_TEST(foo.a == 42); + BOOST_TEST(foo.b == "baz"); + + const toml::value v2(foo); + + BOOST_TEST(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_TEST(bar.a == 42); + BOOST_TEST(bar.b == "baz"); + + const toml::value v2(bar); + + BOOST_TEST(v == v2); +} From 190636b79193910aa119d508aae709474a91b7b9 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Sat, 16 Mar 2019 15:52:22 +0900 Subject: [PATCH 5/8] fix: support getting a container of external types --- tests/test_extended_conversions.cpp | 34 +++++++++++++++++++++++++++++ toml/get.hpp | 18 +++++++++++++-- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/tests/test_extended_conversions.cpp b/tests/test_extended_conversions.cpp index dd20758..0d4ff25 100644 --- a/tests/test_extended_conversions.cpp +++ b/tests/test_extended_conversions.cpp @@ -80,3 +80,37 @@ BOOST_AUTO_TEST_CASE(test_conversion_by_specialization) BOOST_TEST(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_TEST(foos.size() == 4ul); + BOOST_TEST(foos.at(0).a == 42); + BOOST_TEST(foos.at(1).a == 43); + BOOST_TEST(foos.at(2).a == 44); + BOOST_TEST(foos.at(3).a == 45); + + BOOST_TEST(foos.at(0).b == "baz"); + BOOST_TEST(foos.at(1).b == "qux"); + BOOST_TEST(foos.at(2).b == "quux"); + BOOST_TEST(foos.at(3).b == "foobar"); + + const auto bars = toml::get>(v); + BOOST_TEST(bars.size() == 4ul); + BOOST_TEST(bars.at(0).a == 42); + BOOST_TEST(bars.at(1).a == 43); + BOOST_TEST(bars.at(2).a == 44); + BOOST_TEST(bars.at(3).a == 45); + + BOOST_TEST(bars.at(0).b == "baz"); + BOOST_TEST(bars.at(1).b == "qux"); + BOOST_TEST(bars.at(2).b == "quux"); + BOOST_TEST(bars.at(3).b == "foobar"); +} diff --git a/toml/get.hpp b/toml/get.hpp index dc1b495..9d14e8c 100644 --- a/toml/get.hpp +++ b/toml/get.hpp @@ -174,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. @@ -306,7 +320,7 @@ 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> + >::value, std::nullptr_t>::type> T get(const toml::value& v) { T ud; @@ -315,7 +329,7 @@ T get(const toml::value& v) } template> // not a toml::value - >::value, std::nullptr_t>::type = nullptr, + >::value, std::nullptr_t>::type, std::size_t = sizeof(::toml::from) // and has from specialization > T get(const toml::value& v) From 30a41aa71078f9197bb61dd0013a8defefe6f53d Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Sat, 16 Mar 2019 16:15:01 +0900 Subject: [PATCH 6/8] fix: use older style in BOOST_TEST --- tests/test_extended_conversions.cpp | 48 ++++++++++++++--------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/test_extended_conversions.cpp b/tests/test_extended_conversions.cpp index 0d4ff25..fd5d88d 100644 --- a/tests/test_extended_conversions.cpp +++ b/tests/test_extended_conversions.cpp @@ -60,12 +60,12 @@ BOOST_AUTO_TEST_CASE(test_conversion_by_member_methods) const toml::value v{{"a", 42}, {"b", "baz"}}; const auto foo = toml::get(v); - BOOST_TEST(foo.a == 42); - BOOST_TEST(foo.b == "baz"); + BOOST_CHECK_EQUAL(foo.a, 42); + BOOST_CHECK_EQUAL(foo.b, "baz"); const toml::value v2(foo); - BOOST_TEST(v == v2); + BOOST_CHECK_EQUAL(v, v2); } BOOST_AUTO_TEST_CASE(test_conversion_by_specialization) @@ -73,12 +73,12 @@ BOOST_AUTO_TEST_CASE(test_conversion_by_specialization) const toml::value v{{"a", 42}, {"b", "baz"}}; const auto bar = toml::get(v); - BOOST_TEST(bar.a == 42); - BOOST_TEST(bar.b == "baz"); + BOOST_CHECK_EQUAL(bar.a, 42); + BOOST_CHECK_EQUAL(bar.b, "baz"); const toml::value v2(bar); - BOOST_TEST(v == v2); + BOOST_CHECK_EQUAL(v, v2); } BOOST_AUTO_TEST_CASE(test_recursive_conversion) @@ -91,26 +91,26 @@ BOOST_AUTO_TEST_CASE(test_recursive_conversion) }; const auto foos = toml::get>(v); - BOOST_TEST(foos.size() == 4ul); - BOOST_TEST(foos.at(0).a == 42); - BOOST_TEST(foos.at(1).a == 43); - BOOST_TEST(foos.at(2).a == 44); - BOOST_TEST(foos.at(3).a == 45); + 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_TEST(foos.at(0).b == "baz"); - BOOST_TEST(foos.at(1).b == "qux"); - BOOST_TEST(foos.at(2).b == "quux"); - BOOST_TEST(foos.at(3).b == "foobar"); + 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_TEST(bars.size() == 4ul); - BOOST_TEST(bars.at(0).a == 42); - BOOST_TEST(bars.at(1).a == 43); - BOOST_TEST(bars.at(2).a == 44); - BOOST_TEST(bars.at(3).a == 45); + 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_TEST(bars.at(0).b == "baz"); - BOOST_TEST(bars.at(1).b == "qux"); - BOOST_TEST(bars.at(2).b == "quux"); - BOOST_TEST(bars.at(3).b == "foobar"); + 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"); } From 43014c6619232166b1883e9944477dfdf65cc3cf Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Sat, 16 Mar 2019 16:24:10 +0900 Subject: [PATCH 7/8] fix: remove redefined default template argument --- toml/get.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/toml/get.hpp b/toml/get.hpp index ffc8747..6aad019 100644 --- a/toml/get.hpp +++ b/toml/get.hpp @@ -329,9 +329,7 @@ T get(const toml::value& v) } template> // not a toml::value - >::value, std::nullptr_t>::type, - std::size_t = sizeof(::toml::from) // and has from specialization - > + >::value, std::nullptr_t>::type, std::size_t> // and has from T get(const toml::value& v) { return ::toml::from::from_toml(v); From cad8f5125651149e3f4297a8111839ad8bf7acb2 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Sat, 16 Mar 2019 16:56:37 +0900 Subject: [PATCH 8/8] doc: add explanation of conversions to README --- README.md | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/README.md b/README.md index 2861b34..2695491 100644 --- a/README.md +++ b/README.md @@ -423,6 +423,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