From 25e740d7baf174b114b31264b51788dcc15bce2c Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Fri, 21 Jul 2023 18:03:38 +0300 Subject: [PATCH] fix inconsistent choice of init list constructor On some compilers value{x} invokes initializer_list constructor, on others it is equivalent to value(x). This is very problematic, but this isn't something we can fix. On the other hand, we CAN make init list construction to be equivalent to value(x), if the size of init list is one. This commit does exactly that. --- include/boost/json/impl/value.ipp | 21 ++++++++++++++++++--- include/boost/json/value.hpp | 29 ++++++++++++++++++++++++----- test/value.cpp | 11 +++++++++++ 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/include/boost/json/impl/value.ipp b/include/boost/json/impl/value.ipp index b4e80f036..12d60f5ff 100644 --- a/include/boost/json/impl/value.ipp +++ b/include/boost/json/impl/value.ipp @@ -231,13 +231,28 @@ value( storage_ptr sp) { if(value_ref::maybe_object(init)) + { ::new(&obj_) object( value_ref::make_object( init, std::move(sp))); + } else - ::new(&arr_) array( - value_ref::make_array( - init, std::move(sp))); + { +#ifndef BOOST_JSON_LEGACY_INIT_LIST_BEHAVIOR + if( init.size() == 1 ) + { + ::new(&sca_) scalar(); + value temp = init.begin()->make_value( std::move(sp) ); + swap(temp); + } + else +#endif + { + ::new(&arr_) array( + value_ref::make_array( + init, std::move(sp))); + } + } } //---------------------------------------------------------- diff --git a/include/boost/json/value.hpp b/include/boost/json/value.hpp index 24df80a7e..a642acec6 100644 --- a/include/boost/json/value.hpp +++ b/include/boost/json/value.hpp @@ -1014,11 +1014,16 @@ class value /** Construct from an initializer-list - If the initializer list consists of key/value - pairs, an @ref object is created. Otherwise - an @ref array is created. The contents of the - initializer list are copied to the newly constructed - value using the specified memory resource. + @li If the initializer list consists of key/value + pairs, an @ref object is created; otherwise, + + @li if the size of the initializer list is exactly 1, the object is + constructed directly from that sole element; otherwise, + + @li an @ref array is created. + + The contents of the initializer list are copied to the newly + constructed value using the specified memory resource. @par Complexity Linear in `init.size()`. @@ -1032,6 +1037,20 @@ class value @param sp A pointer to the @ref memory_resource to use. The container will acquire shared ownership of the memory resource. + + @par Note + The previous behavior of this constructor was to always + construct either an @ref object or an @ref array. In practice though, + several C++ implementations did not treat `value{x}` as a constructor + from initializer list. This effectively resulted in different behavior + on different implementations.
+ + If you need the legacy behavior define macro + `BOOST_JSON_LEGACY_INIT_LIST_BEHAVIOR` when you are building the + library. The macro and the functionality will be deprecated in the + future and then removed, so we urge you to change your code for the new + behavior as soon as possible. The simplest way to create an @ref array + with 1 element using an initializer list is via `array{x}`. */ BOOST_JSON_DECL value( diff --git a/test/value.cpp b/test/value.cpp index 66ce1c6f7..315e77028 100644 --- a/test/value.cpp +++ b/test/value.cpp @@ -2131,6 +2131,17 @@ class value_test void testInitList() { + { + value jv{}; + BOOST_TEST( jv.is_null() ); + } +#ifndef BOOST_JSON_LEGACY_INIT_LIST_BEHAVIOR + { + value jv{0}; + BOOST_TEST( jv == 0 ); + } +#endif + check_array(value{0,0,0}, 0, 0, 0); check_array(value{false,false,false}, false, false, false); check_array(value{false,2,false}, false, 2, false);