From 127b34dbef0c19e22a1ca30e3ebfd4e5d679c9ab Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Thu, 7 Nov 2024 09:50:22 +0100 Subject: [PATCH] Change string copy policy: only string literal are stored by pointer --- CHANGELOG.md | 1 + extras/tests/JsonArray/CMakeLists.txt | 1 - extras/tests/JsonArray/add.cpp | 151 +++++++++++------- extras/tests/JsonArray/std_string.cpp | 34 ---- extras/tests/JsonArray/subscript.cpp | 92 +++++------ extras/tests/JsonDocument/ElementProxy.cpp | 37 ++++- extras/tests/JsonDocument/MemberProxy.cpp | 42 ++++- extras/tests/JsonDocument/add.cpp | 13 +- extras/tests/JsonDocument/remove.cpp | 12 +- extras/tests/JsonDocument/set.cpp | 27 +++- extras/tests/JsonDocument/subscript.cpp | 71 ++++++++ extras/tests/JsonVariant/set.cpp | 24 ++- extras/tests/JsonVariant/types.cpp | 42 +++-- extras/tests/JsonVariantConst/subscript.cpp | 11 +- extras/tests/Misc/StringAdapters.cpp | 15 +- extras/tests/Misc/TypeTraits.cpp | 17 ++ .../MsgPackSerializer/serializeVariant.cpp | 3 +- src/ArduinoJson/Array/ArrayData.hpp | 2 +- src/ArduinoJson/Array/JsonArray.hpp | 3 +- src/ArduinoJson/Document/JsonDocument.hpp | 16 +- src/ArduinoJson/Object/JsonObject.hpp | 11 +- src/ArduinoJson/Object/JsonObjectConst.hpp | 4 +- src/ArduinoJson/Object/MemberProxy.hpp | 2 +- src/ArduinoJson/Polyfills/type_traits.hpp | 1 + .../Polyfills/type_traits/decay.hpp | 33 ++++ .../Strings/Adapters/JsonString.hpp | 2 +- .../Strings/Adapters/RamString.hpp | 18 +-- .../Strings/Adapters/StringLiteral.hpp | 25 +++ src/ArduinoJson/Strings/IsString.hpp | 2 +- src/ArduinoJson/Strings/StringAdapter.hpp | 28 +++- src/ArduinoJson/Strings/StringAdapters.hpp | 5 +- src/ArduinoJson/Variant/ConverterImpl.hpp | 2 +- src/ArduinoJson/Variant/JsonVariantConst.hpp | 9 +- src/ArduinoJson/Variant/VariantData.hpp | 2 +- src/ArduinoJson/Variant/VariantRefBase.hpp | 21 +-- .../Variant/VariantRefBaseImpl.hpp | 8 +- 36 files changed, 541 insertions(+), 246 deletions(-) delete mode 100644 extras/tests/JsonArray/std_string.cpp create mode 100644 src/ArduinoJson/Polyfills/type_traits/decay.hpp create mode 100644 src/ArduinoJson/Strings/Adapters/StringLiteral.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index d95d6bc59..275e729db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ HEAD * Fix support for NUL characters in `deserializeJson()` * Make `ElementProxy` and `MemberProxy` non-copyable +* Change string copy policy: only string literal are stored by pointer > ### BREAKING CHANGES > diff --git a/extras/tests/JsonArray/CMakeLists.txt b/extras/tests/JsonArray/CMakeLists.txt index bb9719a37..2ee1a34de 100644 --- a/extras/tests/JsonArray/CMakeLists.txt +++ b/extras/tests/JsonArray/CMakeLists.txt @@ -13,7 +13,6 @@ add_executable(JsonArrayTests nesting.cpp remove.cpp size.cpp - std_string.cpp subscript.cpp unbound.cpp ) diff --git a/extras/tests/JsonArray/add.cpp b/extras/tests/JsonArray/add.cpp index 1527670ac..f1ca5f208 100644 --- a/extras/tests/JsonArray/add.cpp +++ b/extras/tests/JsonArray/add.cpp @@ -17,31 +17,112 @@ TEST_CASE("JsonArray::add(T)") { SECTION("int") { array.add(123); + REQUIRE(123 == array[0].as()); REQUIRE(array[0].is()); REQUIRE(array[0].is()); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + }); } SECTION("double") { array.add(123.45); + REQUIRE(123.45 == array[0].as()); REQUIRE(array[0].is()); REQUIRE_FALSE(array[0].is()); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + }); } SECTION("bool") { array.add(true); - REQUIRE(true == array[0].as()); + + REQUIRE(array[0].as() == true); REQUIRE(array[0].is()); REQUIRE_FALSE(array[0].is()); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + }); + } + + SECTION("string literal") { + array.add("hello"); + + REQUIRE(array[0].as() == "hello"); + REQUIRE(array[0].is()); + REQUIRE(array[0].is() == false); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + }); + } + + SECTION("std::string") { + array.add("hello"_s); + + REQUIRE(array[0].as() == "hello"); + REQUIRE(array[0].is() == true); + REQUIRE(array[0].is() == false); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("hello")), + }); } SECTION("const char*") { const char* str = "hello"; array.add(str); - REQUIRE(str == array[0].as()); - REQUIRE(array[0].is()); - REQUIRE_FALSE(array[0].is()); + + REQUIRE(array[0].as() == "hello"); + REQUIRE(array[0].as() != str); + REQUIRE(array[0].is() == true); + REQUIRE(array[0].is() == false); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("hello")), + }); + } + + SECTION("serialized(const char*)") { + array.add(serialized("{}")); + + REQUIRE(doc.as() == "[{}]"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("{}")), + }); + } + + SECTION("serialized(char*)") { + array.add(serialized(const_cast("{}"))); + + REQUIRE(doc.as() == "[{}]"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("{}")), + }); + } + + SECTION("serialized(std::string)") { + array.add(serialized("{}"_s)); + + REQUIRE(doc.as() == "[{}]"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("{}")), + }); + } + + SECTION("serialized(std::string)") { + array.add(serialized("\0XX"_s)); + + REQUIRE(doc.as() == "[\0XX]"_s); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString(" XX")), + }); } #ifdef HAS_VARIABLE_LENGTH_ARRAY @@ -52,7 +133,12 @@ TEST_CASE("JsonArray::add(T)") { array.add(vla); - REQUIRE("world"_s == array[0]); + strcpy(vla, "hello"); + REQUIRE(array[0] == "world"_s); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("hello")), + }); } #endif @@ -99,61 +185,6 @@ TEST_CASE("JsonArray::add(T)") { REQUIRE(str == array[0]); } - - SECTION("should not duplicate const char*") { - array.add("world"); - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - }); - } - - SECTION("should duplicate char*") { - array.add(const_cast("world")); - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - Allocate(sizeofString("world")), - }); - } - - SECTION("should duplicate std::string") { - array.add("world"_s); - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - Allocate(sizeofString("world")), - }); - } - - SECTION("should duplicate serialized(const char*)") { - array.add(serialized("{}")); - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - Allocate(sizeofString("{}")), - }); - } - - SECTION("should duplicate serialized(char*)") { - array.add(serialized(const_cast("{}"))); - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - Allocate(sizeofString("{}")), - }); - } - - SECTION("should duplicate serialized(std::string)") { - array.add(serialized("{}"_s)); - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - Allocate(sizeofString("{}")), - }); - } - - SECTION("should duplicate serialized(std::string)") { - array.add(serialized("\0XX"_s)); - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - Allocate(sizeofString(" XX")), - }); - } } TEST_CASE("JsonArray::add()") { diff --git a/extras/tests/JsonArray/std_string.cpp b/extras/tests/JsonArray/std_string.cpp deleted file mode 100644 index d6cae2e62..000000000 --- a/extras/tests/JsonArray/std_string.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// ArduinoJson - https://arduinojson.org -// Copyright © 2014-2024, Benoit BLANCHON -// MIT License - -#include -#include - -#include "Literals.hpp" - -static void eraseString(std::string& str) { - char* p = const_cast(str.c_str()); - while (*p) - *p++ = '*'; -} - -TEST_CASE("std::string") { - JsonDocument doc; - JsonArray array = doc.to(); - - SECTION("add()") { - std::string value("hello"); - array.add(value); - eraseString(value); - REQUIRE("hello"_s == array[0]); - } - - SECTION("operator[]") { - std::string value("world"); - array.add("hello"); - array[0] = value; - eraseString(value); - REQUIRE("world"_s == array[0]); - } -} diff --git a/extras/tests/JsonArray/subscript.cpp b/extras/tests/JsonArray/subscript.cpp index b0332f856..40b173610 100644 --- a/extras/tests/JsonArray/subscript.cpp +++ b/extras/tests/JsonArray/subscript.cpp @@ -59,14 +59,55 @@ TEST_CASE("JsonArray::operator[]") { REQUIRE(false == array[0].is()); } + SECTION("string literal") { + array[0] = "hello"; + + REQUIRE(array[0].as() == "hello"); + REQUIRE(true == array[0].is()); + REQUIRE(false == array[0].is()); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("world")), + }); + } + SECTION("const char*") { const char* str = "hello"; - array[0] = str; - REQUIRE(str == array[0].as()); + + REQUIRE(array[0].as() == "hello"); + REQUIRE(true == array[0].is()); + REQUIRE(false == array[0].is()); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("world")), + }); + } + + SECTION("std::string") { + array[0] = "hello"_s; + + REQUIRE(array[0].as() == "hello"); REQUIRE(true == array[0].is()); REQUIRE(false == array[0].is()); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("world")), + }); + } + +#ifdef HAS_VARIABLE_LENGTH_ARRAY + SECTION("VLA") { + size_t i = 16; + char vla[i]; + strcpy(vla, "world"); + + array.add("hello"); + array[0] = vla; + + REQUIRE(array[0] == "world"_s); } +#endif SECTION("nested array") { JsonDocument doc2; @@ -114,58 +155,11 @@ TEST_CASE("JsonArray::operator[]") { REQUIRE(str == array[0]); } - SECTION("should not duplicate const char*") { - array[0] = "world"; - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - }); - } - - SECTION("should duplicate char*") { - array[0] = const_cast("world"); - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - Allocate(sizeofString("world")), - }); - } - - SECTION("should duplicate std::string") { - array[0] = "world"_s; - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - Allocate(sizeofString("world")), - }); - } - SECTION("array[0].to()") { JsonObject obj = array[0].to(); REQUIRE(obj.isNull() == false); } -#ifdef HAS_VARIABLE_LENGTH_ARRAY - SECTION("set(VLA)") { - size_t i = 16; - char vla[i]; - strcpy(vla, "world"); - - array.add("hello"); - array[0].set(vla); - - REQUIRE("world"_s == array[0]); - } - - SECTION("operator=(VLA)") { - size_t i = 16; - char vla[i]; - strcpy(vla, "world"); - - array.add("hello"); - array[0] = vla; - - REQUIRE("world"_s == array[0]); - } -#endif - SECTION("Use a JsonVariant as index") { array[0] = 1; array[1] = 2; diff --git a/extras/tests/JsonDocument/ElementProxy.cpp b/extras/tests/JsonDocument/ElementProxy.cpp index 7de4d6abe..387dc8847 100644 --- a/extras/tests/JsonDocument/ElementProxy.cpp +++ b/extras/tests/JsonDocument/ElementProxy.cpp @@ -5,37 +5,60 @@ #include #include +#include "Allocators.hpp" #include "Literals.hpp" using ElementProxy = ArduinoJson::detail::ElementProxy; TEST_CASE("ElementProxy::add()") { - JsonDocument doc; + SpyingAllocator spy; + JsonDocument doc(&spy); doc.add(); const ElementProxy& ep = doc[0]; - SECTION("add(int)") { + SECTION("integer") { ep.add(42); REQUIRE(doc.as() == "[[42]]"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + }); } - SECTION("add(const char*)") { + SECTION("string literal") { ep.add("world"); REQUIRE(doc.as() == "[[\"world\"]]"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + }); + } + + SECTION("const char*") { + const char* s = "world"; + ep.add(s); + + REQUIRE(doc.as() == "[[\"world\"]]"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("world")), + }); } - SECTION("add(char[])") { + SECTION("char[]") { char s[] = "world"; ep.add(s); strcpy(s, "!!!!!"); REQUIRE(doc.as() == "[[\"world\"]]"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("world")), + }); } #ifdef HAS_VARIABLE_LENGTH_ARRAY - SECTION("set(vla)") { + SECTION("VLA") { size_t i = 8; char vla[i]; strcpy(vla, "world"); @@ -43,6 +66,10 @@ TEST_CASE("ElementProxy::add()") { ep.add(vla); REQUIRE(doc.as() == "[[\"world\"]]"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("world")), + }); } #endif } diff --git a/extras/tests/JsonDocument/MemberProxy.cpp b/extras/tests/JsonDocument/MemberProxy.cpp index 79f314c0a..4eb7468c1 100644 --- a/extras/tests/JsonDocument/MemberProxy.cpp +++ b/extras/tests/JsonDocument/MemberProxy.cpp @@ -15,23 +15,53 @@ using ArduinoJson::detail::sizeofArray; using ArduinoJson::detail::sizeofObject; TEST_CASE("MemberProxy::add()") { - JsonDocument doc; + SpyingAllocator spy; + JsonDocument doc(&spy); const auto& mp = doc["hello"]; - SECTION("add(int)") { + SECTION("integer") { mp.add(42); REQUIRE(doc.as() == "{\"hello\":[42]}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + }); } - SECTION("add(const char*)") { + SECTION("string literal") { mp.add("world"); REQUIRE(doc.as() == "{\"hello\":[\"world\"]}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + }); + } + + SECTION("const char*") { + const char* temp = "world"; + mp.add(temp); + + REQUIRE(doc.as() == "{\"hello\":[\"world\"]}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("world")), + }); + } + + SECTION("char[]") { + char temp[] = "world"; + mp.add(temp); + + REQUIRE(doc.as() == "{\"hello\":[\"world\"]}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("world")), + + }); } #ifdef HAS_VARIABLE_LENGTH_ARRAY - SECTION("add(vla)") { + SECTION("VLA") { size_t i = 16; char vla[i]; strcpy(vla, "world"); @@ -39,6 +69,10 @@ TEST_CASE("MemberProxy::add()") { mp.add(vla); REQUIRE(doc.as() == "{\"hello\":[\"world\"]}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("world")), + }); } #endif } diff --git a/extras/tests/JsonDocument/add.cpp b/extras/tests/JsonDocument/add.cpp index 31aa6c2f1..8a1c48e3c 100644 --- a/extras/tests/JsonDocument/add.cpp +++ b/extras/tests/JsonDocument/add.cpp @@ -26,7 +26,7 @@ TEST_CASE("JsonDocument::add(T)") { }); } - SECTION("const char*") { + SECTION("string literal") { doc.add("hello"); REQUIRE(doc.as() == "[\"hello\"]"); @@ -35,6 +35,17 @@ TEST_CASE("JsonDocument::add(T)") { }); } + SECTION("const char*") { + const char* value = "hello"; + doc.add(value); + + REQUIRE(doc.as() == "[\"hello\"]"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("hello")), + }); + } + SECTION("std::string") { doc.add("example"_s); doc.add("example"_s); diff --git a/extras/tests/JsonDocument/remove.cpp b/extras/tests/JsonDocument/remove.cpp index afabf4eb1..1656a4d22 100644 --- a/extras/tests/JsonDocument/remove.cpp +++ b/extras/tests/JsonDocument/remove.cpp @@ -20,11 +20,21 @@ TEST_CASE("JsonDocument::remove()") { REQUIRE(doc.as() == "[1,3]"); } + SECTION("string literal") { + doc["a"] = 1; + doc["a\0b"_s] = 2; + doc["b"] = 3; + + doc.remove("a\0b"); + + REQUIRE(doc.as() == "{\"a\":1,\"b\":3}"); + } + SECTION("remove(const char *)") { doc["a"] = 1; doc["b"] = 2; - doc.remove("a"); + doc.remove(static_cast("a")); REQUIRE(doc.as() == "{\"b\":2}"); } diff --git a/extras/tests/JsonDocument/set.cpp b/extras/tests/JsonDocument/set.cpp index ce9de42a1..1205acf55 100644 --- a/extras/tests/JsonDocument/set.cpp +++ b/extras/tests/JsonDocument/set.cpp @@ -11,6 +11,21 @@ TEST_CASE("JsonDocument::set()") { SpyingAllocator spy; JsonDocument doc(&spy); + SECTION("nullptr") { + doc.set(nullptr); + + REQUIRE(doc.isNull()); + REQUIRE(spy.log() == AllocatorLog{}); + } + + SECTION("integer&") { + int toto = 42; + doc.set(toto); + + REQUIRE(doc.as() == "42"); + REQUIRE(spy.log() == AllocatorLog{}); + } + SECTION("integer") { doc.set(42); @@ -18,13 +33,23 @@ TEST_CASE("JsonDocument::set()") { REQUIRE(spy.log() == AllocatorLog{}); } - SECTION("const char*") { + SECTION("string literal") { doc.set("example"); REQUIRE(doc.as() == "example"_s); REQUIRE(spy.log() == AllocatorLog{}); } + SECTION("const char*") { + const char* value = "example"; + doc.set(value); + + REQUIRE(doc.as() == "example"_s); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofString("example")), + }); + } + SECTION("std::string") { doc.set("example"_s); diff --git a/extras/tests/JsonDocument/subscript.cpp b/extras/tests/JsonDocument/subscript.cpp index 250ce1dd1..2150d15cf 100644 --- a/extras/tests/JsonDocument/subscript.cpp +++ b/extras/tests/JsonDocument/subscript.cpp @@ -5,6 +5,7 @@ #include #include +#include "Allocators.hpp" #include "Literals.hpp" TEST_CASE("JsonDocument::operator[]") { @@ -16,8 +17,16 @@ TEST_CASE("JsonDocument::operator[]") { doc["abc\0d"_s] = "ABCD"; SECTION("const char*") { + const char* key = "abc"; + REQUIRE(doc[key] == "ABC"); + REQUIRE(cdoc[key] == "ABC"); + } + + SECTION("string literal") { REQUIRE(doc["abc"] == "ABC"); REQUIRE(cdoc["abc"] == "ABC"); + REQUIRE(doc["abc\0d"] == "ABCD"); + REQUIRE(cdoc["abc\0d"] == "ABCD"); } SECTION("std::string") { @@ -94,3 +103,65 @@ TEST_CASE("JsonDocument automatically promotes to array") { REQUIRE(doc.as() == "[null,null,2]"); } + +TEST_CASE("JsonDocument::operator[] key storage") { + SpyingAllocator spy; + JsonDocument doc(&spy); + + SECTION("string literal") { + doc["hello"] = 0; + + REQUIRE(doc.as() == "{\"hello\":0}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + }); + } + + SECTION("const char*") { + const char* key = "hello"; + doc[key] = 0; + + REQUIRE(doc.as() == "{\"hello\":0}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("hello")), + }); + } + + SECTION("char[]") { + char key[] = "hello"; + doc[key] = 0; + + REQUIRE(doc.as() == "{\"hello\":0}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("hello")), + }); + } + + SECTION("std::string") { + doc["hello"_s] = 0; + + REQUIRE(doc.as() == "{\"hello\":0}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("hello")), + }); + } +#if defined(HAS_VARIABLE_LENGTH_ARRAY) && \ + !defined(SUBSCRIPT_CONFLICTS_WITH_BUILTIN_OPERATOR) + SECTION("VLA") { + size_t i = 16; + char vla[i]; + strcpy(vla, "hello"); + + doc[vla] = 0; + + REQUIRE(doc.as() == "{\"hello\":0}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("hello")), + }); + } +#endif +} diff --git a/extras/tests/JsonVariant/set.cpp b/extras/tests/JsonVariant/set.cpp index b10f95053..8e5c1a29d 100644 --- a/extras/tests/JsonVariant/set.cpp +++ b/extras/tests/JsonVariant/set.cpp @@ -17,6 +17,15 @@ TEST_CASE("JsonVariant::set() when there is enough memory") { JsonDocument doc(&spy); JsonVariant variant = doc.to(); + SECTION("string literal") { + bool result = variant.set("hello\0world"); + + REQUIRE(result == true); + CHECK(variant == + "hello"_s); // linked string cannot contain '\0' at the moment + CHECK(spy.log() == AllocatorLog{}); + } + SECTION("const char*") { char str[16]; @@ -25,8 +34,10 @@ TEST_CASE("JsonVariant::set() when there is enough memory") { strcpy(str, "world"); REQUIRE(result == true); - REQUIRE(variant == "world"); // stores by pointer - REQUIRE(spy.log() == AllocatorLog{}); + REQUIRE(variant == "hello"); // stores by copy + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofString("hello")), + }); } SECTION("(const char*)0") { @@ -34,6 +45,7 @@ TEST_CASE("JsonVariant::set() when there is enough memory") { REQUIRE(result == true); REQUIRE(variant.isNull()); + REQUIRE(variant.as() == nullptr); REQUIRE(spy.log() == AllocatorLog{}); } @@ -105,16 +117,14 @@ TEST_CASE("JsonVariant::set() when there is enough memory") { #endif SECTION("std::string") { - std::string str; - - str = "hello"; + std::string str = "hello\0world"_s; bool result = variant.set(str); str.replace(0, 5, "world"); REQUIRE(result == true); - REQUIRE(variant == "hello"); // stores by copy + REQUIRE(variant == "hello\0world"_s); // stores by copy REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofString("hello")), + Allocate(sizeofString("hello?world")), }); } diff --git a/extras/tests/JsonVariant/types.cpp b/extras/tests/JsonVariant/types.cpp index eb0ee6289..b04c91f1f 100644 --- a/extras/tests/JsonVariant/types.cpp +++ b/extras/tests/JsonVariant/types.cpp @@ -7,14 +7,8 @@ #include #include -template -void checkValue(T expected) { - JsonDocument doc; - JsonVariant variant = doc.to(); - - variant.set(expected); - REQUIRE(expected == variant.as()); -} +#include "Allocators.hpp" +#include "Literals.hpp" template void checkReference(T& expected) { @@ -39,27 +33,29 @@ void checkNumericType() { } TEST_CASE("JsonVariant set()/get()") { + SpyingAllocator spy; + JsonDocument doc(&spy); + JsonVariant variant = doc.to(); + #if ARDUINOJSON_USE_LONG_LONG SECTION("SizeOfJsonInteger") { REQUIRE(8 == sizeof(JsonInteger)); } #endif - SECTION("Null") { - checkValue(NULL); - } - SECTION("const char*") { - checkValue("hello"); - } - SECTION("std::string") { - checkValue("hello"); - } + // /!\ Most test were moved to `JsonVariant/set.cpp` + // TODO: move the remaining tests too SECTION("False") { - checkValue(false); + variant.set(false); + REQUIRE(variant.as() == false); + REQUIRE(spy.log() == AllocatorLog{}); } + SECTION("True") { - checkValue(true); + variant.set(true); + REQUIRE(variant.as() == true); + REQUIRE(spy.log() == AllocatorLog{}); } SECTION("Double") { @@ -129,10 +125,12 @@ TEST_CASE("JsonVariant set()/get()") { #endif SECTION("CanStoreObject") { - JsonDocument doc; - JsonObject object = doc.to(); + JsonDocument doc2; + JsonObject object = doc2.to(); - checkValue(object); + variant.set(object); + REQUIRE(variant.is()); + REQUIRE(variant.as() == object); } } diff --git a/extras/tests/JsonVariantConst/subscript.cpp b/extras/tests/JsonVariantConst/subscript.cpp index 735506ae0..a207c5314 100644 --- a/extras/tests/JsonVariantConst/subscript.cpp +++ b/extras/tests/JsonVariantConst/subscript.cpp @@ -54,13 +54,22 @@ TEST_CASE("JsonVariantConst::operator[]") { object["abc"_s] = "ABC"; object["abc\0d"_s] = "ABCD"; - SECTION("supports const char*") { + SECTION("string literal") { REQUIRE(var["ab"] == "AB"_s); REQUIRE(var["abc"] == "ABC"_s); + REQUIRE(var["abc\0d"] == "ABCD"_s); REQUIRE(var["def"].isNull()); REQUIRE(var[0].isNull()); } + SECTION("const char*") { + REQUIRE(var[static_cast("ab")] == "AB"_s); + REQUIRE(var[static_cast("abc")] == "ABC"_s); + REQUIRE(var[static_cast("abc\0d")] == "ABC"_s); + REQUIRE(var[static_cast("def")].isNull()); + REQUIRE(var[static_cast(0)].isNull()); + } + SECTION("supports std::string") { REQUIRE(var["ab"_s] == "AB"_s); REQUIRE(var["abc"_s] == "ABC"_s); diff --git a/extras/tests/Misc/StringAdapters.cpp b/extras/tests/Misc/StringAdapters.cpp index b33ab990f..dbf5f06c4 100644 --- a/extras/tests/Misc/StringAdapters.cpp +++ b/extras/tests/Misc/StringAdapters.cpp @@ -16,20 +16,29 @@ using ArduinoJson::JsonString; using namespace ArduinoJson::detail; TEST_CASE("adaptString()") { + SECTION("string literal") { + auto s = adaptString("bravo\0alpha"); + + CHECK(s.isNull() == false); + CHECK(s.size() == 11); + CHECK(s.isLinked() == true); + } + SECTION("null const char*") { auto s = adaptString(static_cast(0)); CHECK(s.isNull() == true); CHECK(s.size() == 0); - CHECK(s.isLinked() == true); } SECTION("non-null const char*") { - auto s = adaptString("bravo"); + const char* p = "bravo"; + auto s = adaptString(p); CHECK(s.isNull() == false); CHECK(s.size() == 5); - CHECK(s.isLinked() == true); + CHECK(s.isLinked() == false); + CHECK(s.data() == p); } SECTION("null const char* + size") { diff --git a/extras/tests/Misc/TypeTraits.cpp b/extras/tests/Misc/TypeTraits.cpp index 7beb46585..5d0810616 100644 --- a/extras/tests/Misc/TypeTraits.cpp +++ b/extras/tests/Misc/TypeTraits.cpp @@ -211,6 +211,23 @@ TEST_CASE("Polyfills/type_traits") { CHECK(is_enum::value == false); CHECK(is_enum::value == false); } + + SECTION("remove_cv") { + CHECK(is_same, int>::value); + CHECK(is_same, int>::value); + CHECK(is_same, int>::value); + CHECK(is_same, int>::value); + CHECK(is_same, decltype("toto")>::value); + } + + SECTION("decay") { + CHECK(is_same, int>::value); + CHECK(is_same, int>::value); + CHECK(is_same, int>::value); + CHECK(is_same, int*>::value); + CHECK(is_same, int*>::value); + CHECK(is_same, const char*>::value); + } } TEST_CASE("is_std_string") { diff --git a/extras/tests/MsgPackSerializer/serializeVariant.cpp b/extras/tests/MsgPackSerializer/serializeVariant.cpp index 43ca3ff43..505b56ced 100644 --- a/extras/tests/MsgPackSerializer/serializeVariant.cpp +++ b/extras/tests/MsgPackSerializer/serializeVariant.cpp @@ -139,7 +139,8 @@ TEST_CASE("serialize MsgPack value") { SECTION("str 32") { std::string shortest(65536, '?'); - checkVariant(shortest.c_str(), "\xDB\x00\x01\x00\x00"_s + shortest); + checkVariant(JsonString(shortest.c_str(), true), // force store by pointer + "\xDB\x00\x01\x00\x00"_s + shortest); } SECTION("serialized(const char*)") { diff --git a/src/ArduinoJson/Array/ArrayData.hpp b/src/ArduinoJson/Array/ArrayData.hpp index c2ae1fb39..aba10b179 100644 --- a/src/ArduinoJson/Array/ArrayData.hpp +++ b/src/ArduinoJson/Array/ArrayData.hpp @@ -26,7 +26,7 @@ class ArrayData : public CollectionData { ResourceManager* resources) { if (!array) return false; - return array->addValue(value, resources); + return array->addValue(detail::forward(value), resources); } VariantData* getOrAddElement(size_t index, ResourceManager* resources); diff --git a/src/ArduinoJson/Array/JsonArray.hpp b/src/ArduinoJson/Array/JsonArray.hpp index e9cbca71e..565cff26f 100644 --- a/src/ArduinoJson/Array/JsonArray.hpp +++ b/src/ArduinoJson/Array/JsonArray.hpp @@ -66,7 +66,8 @@ class JsonArray : public detail::VariantOperators { // Appends a value to the array. // https://arduinojson.org/v7/api/jsonarray/add/ - template + template ::value>> bool add(T* value) const { return detail::ArrayData::addValue(data_, value, resources_); } diff --git a/src/ArduinoJson/Document/JsonDocument.hpp b/src/ArduinoJson/Document/JsonDocument.hpp index 9916e2965..403930809 100644 --- a/src/ArduinoJson/Document/JsonDocument.hpp +++ b/src/ArduinoJson/Document/JsonDocument.hpp @@ -144,7 +144,8 @@ class JsonDocument : public detail::VariantOperators { // Replaces the root with the specified value. // https://arduinojson.org/v7/api/jsondocument/set/ - template + template ::value>> bool set(TChar* src) { return to().set(src); } @@ -197,7 +198,7 @@ class JsonDocument : public detail::VariantOperators { // https://arduinojson.org/v7/api/jsondocument/subscript/ template detail::enable_if_t< - detail::IsString::value, + detail::IsString::value && !detail::is_const::value, detail::MemberProxy>> operator[](TChar* key) { return {*this, detail::adaptString(key)}; @@ -215,7 +216,9 @@ class JsonDocument : public detail::VariantOperators { // Gets a root object's member. // https://arduinojson.org/v7/api/jsondocument/subscript/ template - detail::enable_if_t::value, JsonVariantConst> + detail::enable_if_t::value && + !detail::is_const::value, + JsonVariantConst> operator[](TChar* key) const { return JsonVariantConst( data_.getMember(detail::adaptString(key), &resources_), &resources_); @@ -273,7 +276,8 @@ class JsonDocument : public detail::VariantOperators { // Appends a value to the root array. // https://arduinojson.org/v7/api/jsondocument/add/ - template + template ::value>> bool add(TChar* value) { return data_.addValue(value, &resources_); } @@ -289,7 +293,9 @@ class JsonDocument : public detail::VariantOperators { // Removes a member of the root object. // https://arduinojson.org/v7/api/jsondocument/remove/ template - detail::enable_if_t::value> remove(TChar* key) { + detail::enable_if_t::value && + !detail::is_const::value> + remove(TChar* key) { detail::VariantData::removeMember(getData(), detail::adaptString(key), getResourceManager()); } diff --git a/src/ArduinoJson/Object/JsonObject.hpp b/src/ArduinoJson/Object/JsonObject.hpp index afcb8d753..854d9b423 100644 --- a/src/ArduinoJson/Object/JsonObject.hpp +++ b/src/ArduinoJson/Object/JsonObject.hpp @@ -105,15 +105,15 @@ class JsonObject : public detail::VariantOperators { detail::enable_if_t< detail::IsString::value, detail::MemberProxy>> - operator[](const TString& key) const { - return {*this, detail::adaptString(key)}; + operator[](TString&& key) const { + return {*this, detail::adaptString(detail::forward(key))}; } // Gets or sets the member with specified key. // https://arduinojson.org/v7/api/jsonobject/subscript/ template detail::enable_if_t< - detail::IsString::value, + detail::IsString::value && !detail::is_const::value, detail::MemberProxy>> operator[](TChar* key) const { return {*this, detail::adaptString(key)}; @@ -175,8 +175,9 @@ class JsonObject : public detail::VariantOperators { // https://arduinojson.org/v7/api/jsonobject/containskey/ template ARDUINOJSON_DEPRECATED("use obj[\"key\"].is() instead") - detail::enable_if_t::value, bool> containsKey( - TChar* key) const { + detail::enable_if_t::value && + !detail::is_const::value, + bool> containsKey(TChar* key) const { return detail::ObjectData::getMember(data_, detail::adaptString(key), resources_) != 0; } diff --git a/src/ArduinoJson/Object/JsonObjectConst.hpp b/src/ArduinoJson/Object/JsonObjectConst.hpp index 6b0de2025..91d9dca5c 100644 --- a/src/ArduinoJson/Object/JsonObjectConst.hpp +++ b/src/ArduinoJson/Object/JsonObjectConst.hpp @@ -109,7 +109,9 @@ class JsonObjectConst : public detail::VariantOperators { // Gets the member with specified key. // https://arduinojson.org/v7/api/jsonobjectconst/subscript/ template - detail::enable_if_t::value, JsonVariantConst> + detail::enable_if_t::value && + !detail::is_const::value, + JsonVariantConst> operator[](TChar* key) const { return JsonVariantConst(detail::ObjectData::getMember( data_, detail::adaptString(key), resources_), diff --git a/src/ArduinoJson/Object/MemberProxy.hpp b/src/ArduinoJson/Object/MemberProxy.hpp index 2d297585b..ffd89939d 100644 --- a/src/ArduinoJson/Object/MemberProxy.hpp +++ b/src/ArduinoJson/Object/MemberProxy.hpp @@ -39,7 +39,7 @@ class MemberProxy return *this; } - template + template ::value>> MemberProxy& operator=(T* src) { this->set(src); return *this; diff --git a/src/ArduinoJson/Polyfills/type_traits.hpp b/src/ArduinoJson/Polyfills/type_traits.hpp index 3004695a3..471d352cf 100644 --- a/src/ArduinoJson/Polyfills/type_traits.hpp +++ b/src/ArduinoJson/Polyfills/type_traits.hpp @@ -5,6 +5,7 @@ #pragma once #include "type_traits/conditional.hpp" +#include "type_traits/decay.hpp" #include "type_traits/enable_if.hpp" #include "type_traits/function_traits.hpp" #include "type_traits/integral_constant.hpp" diff --git a/src/ArduinoJson/Polyfills/type_traits/decay.hpp b/src/ArduinoJson/Polyfills/type_traits/decay.hpp new file mode 100644 index 000000000..29b4b6eeb --- /dev/null +++ b/src/ArduinoJson/Polyfills/type_traits/decay.hpp @@ -0,0 +1,33 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2024, Benoit BLANCHON +// MIT License + +#pragma once + +#include // size_t + +#include + +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE + +template +struct decay { + using type = T; +}; + +template +struct decay : decay {}; + +template +struct decay : decay {}; + +template +struct decay : decay {}; + +template +struct decay : decay {}; + +template +using decay_t = typename decay::type; + +ARDUINOJSON_END_PRIVATE_NAMESPACE diff --git a/src/ArduinoJson/Strings/Adapters/JsonString.hpp b/src/ArduinoJson/Strings/Adapters/JsonString.hpp index 40cf902b7..ae7e3573e 100644 --- a/src/ArduinoJson/Strings/Adapters/JsonString.hpp +++ b/src/ArduinoJson/Strings/Adapters/JsonString.hpp @@ -14,7 +14,7 @@ template <> struct StringAdapter { using AdaptedString = RamString; - static AdaptedString adapt(const JsonString& s) { + static const AdaptedString& adapt(const JsonString& s) { return s.str_; } }; diff --git a/src/ArduinoJson/Strings/Adapters/RamString.hpp b/src/ArduinoJson/Strings/Adapters/RamString.hpp index 950c24e84..a14b36792 100644 --- a/src/ArduinoJson/Strings/Adapters/RamString.hpp +++ b/src/ArduinoJson/Strings/Adapters/RamString.hpp @@ -20,9 +20,12 @@ struct IsChar class RamString { public: static const size_t typeSortKey = 2; + static constexpr size_t sizeMask = size_t(-1) >> 1; RamString(const char* str, size_t sz, bool linked = false) - : str_(str), size_(sz), linked_(linked) {} + : str_(str), size_(sz & sizeMask), linked_(linked) { + ARDUINOJSON_ASSERT(size_ == sz); + } bool isNull() const { return !str_; @@ -48,8 +51,8 @@ class RamString { protected: const char* str_; - size_t size_; - bool linked_; // TODO: merge with size_ + size_t size_ : sizeof(size_t) * 8 - 1; + bool linked_ : 1; }; template @@ -72,15 +75,6 @@ struct StringAdapter::value>> { } }; -template <> -struct StringAdapter { - using AdaptedString = RamString; - - static AdaptedString adapt(const char* p) { - return AdaptedString(p, p ? ::strlen(p) : 0, true); - } -}; - template struct SizedStringAdapter::value>> { using AdaptedString = RamString; diff --git a/src/ArduinoJson/Strings/Adapters/StringLiteral.hpp b/src/ArduinoJson/Strings/Adapters/StringLiteral.hpp new file mode 100644 index 000000000..cf31dae31 --- /dev/null +++ b/src/ArduinoJson/Strings/Adapters/StringLiteral.hpp @@ -0,0 +1,25 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2024, Benoit BLANCHON +// MIT License + +#pragma once + +#include // size_t +#include // strcmp + +#include +#include +#include + +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE + +template +struct StringAdapter { + using AdaptedString = RamString; + + static AdaptedString adapt(const char (&p)[N]) { + return RamString(p, N - 1, true); + } +}; + +ARDUINOJSON_END_PRIVATE_NAMESPACE diff --git a/src/ArduinoJson/Strings/IsString.hpp b/src/ArduinoJson/Strings/IsString.hpp index 45ef79952..a71655db7 100644 --- a/src/ArduinoJson/Strings/IsString.hpp +++ b/src/ArduinoJson/Strings/IsString.hpp @@ -13,7 +13,7 @@ template struct IsString : false_type {}; template -struct IsString::AdaptedString>> +struct IsString::AdaptedString>> : true_type {}; ARDUINOJSON_END_PRIVATE_NAMESPACE diff --git a/src/ArduinoJson/Strings/StringAdapter.hpp b/src/ArduinoJson/Strings/StringAdapter.hpp index 9cf2e6fdd..50324ffb6 100644 --- a/src/ArduinoJson/Strings/StringAdapter.hpp +++ b/src/ArduinoJson/Strings/StringAdapter.hpp @@ -4,8 +4,17 @@ #pragma once +#include + ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +// a meta function that tells if the type is a string literal (const char[N]) +template +struct IsStringLiteral : false_type {}; + +template +struct IsStringLiteral : true_type {}; + template struct StringAdapter; @@ -13,18 +22,25 @@ template struct SizedStringAdapter; template -typename StringAdapter::AdaptedString adaptString(const TString& s) { - return StringAdapter::adapt(s); +using StringAdapterFor = + StringAdapter::value, TString, + remove_cv_t>>>; + +template +using AdaptedString = typename StringAdapterFor::AdaptedString; + +template +AdaptedString adaptString(TString&& s) { + return StringAdapterFor::adapt(detail::forward(s)); } -template -typename StringAdapter::AdaptedString adaptString(TChar* p) { +template ::value>> +AdaptedString adaptString(TChar* p) { return StringAdapter::adapt(p); } template -typename SizedStringAdapter::AdaptedString adaptString(TChar* p, - size_t n) { +AdaptedString adaptString(TChar* p, size_t n) { return SizedStringAdapter::adapt(p, n); } diff --git a/src/ArduinoJson/Strings/StringAdapters.hpp b/src/ArduinoJson/Strings/StringAdapters.hpp index a558defd6..f9ab23a2c 100644 --- a/src/ArduinoJson/Strings/StringAdapters.hpp +++ b/src/ArduinoJson/Strings/StringAdapters.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #if ARDUINOJSON_ENABLE_PROGMEM @@ -70,8 +71,4 @@ static void stringGetChars(TAdaptedString s, char* p, size_t n) { } } -template -using AdaptedString = - typename StringAdapter>::AdaptedString; - ARDUINOJSON_END_PRIVATE_NAMESPACE diff --git a/src/ArduinoJson/Variant/ConverterImpl.hpp b/src/ArduinoJson/Variant/ConverterImpl.hpp index cd9395953..1709ceaa3 100644 --- a/src/ArduinoJson/Variant/ConverterImpl.hpp +++ b/src/ArduinoJson/Variant/ConverterImpl.hpp @@ -31,7 +31,7 @@ struct Converter { // clang-format on } - static T fromJson(JsonVariantConst src) { + static detail::decay_t fromJson(JsonVariantConst src) { static_assert(!detail::is_same::value, "type 'char*' is not supported, use 'const char*' instead"); diff --git a/src/ArduinoJson/Variant/JsonVariantConst.hpp b/src/ArduinoJson/Variant/JsonVariantConst.hpp index 4389bb8c6..f87b56ddb 100644 --- a/src/ArduinoJson/Variant/JsonVariantConst.hpp +++ b/src/ArduinoJson/Variant/JsonVariantConst.hpp @@ -120,7 +120,9 @@ class JsonVariantConst : public detail::VariantTag, // Gets object's member with specified key. // https://arduinojson.org/v7/api/jsonvariantconst/subscript/ template - detail::enable_if_t::value, JsonVariantConst> + detail::enable_if_t::value && + !detail::is_const::value, + JsonVariantConst> operator[](TChar* key) const { return JsonVariantConst(detail::VariantData::getMember( data_, detail::adaptString(key), resources_), @@ -153,8 +155,9 @@ class JsonVariantConst : public detail::VariantTag, // https://arduinojson.org/v7/api/jsonvariantconst/containskey/ template ARDUINOJSON_DEPRECATED("use obj[\"key\"].is() instead") - detail::enable_if_t::value, bool> containsKey( - TChar* key) const { + detail::enable_if_t::value && + !detail::is_const::value, + bool> containsKey(TChar* key) const { return detail::VariantData::getMember(getData(), detail::adaptString(key), resources_) != 0; } diff --git a/src/ArduinoJson/Variant/VariantData.hpp b/src/ArduinoJson/Variant/VariantData.hpp index 43ee4d807..a37ac1e14 100644 --- a/src/ArduinoJson/Variant/VariantData.hpp +++ b/src/ArduinoJson/Variant/VariantData.hpp @@ -130,7 +130,7 @@ class VariantData { ResourceManager* resources) { if (!var) return false; - return var->addValue(value, resources); + return var->addValue(detail::forward(value), resources); } bool asBoolean(const ResourceManager* resources) const { diff --git a/src/ArduinoJson/Variant/VariantRefBase.hpp b/src/ArduinoJson/Variant/VariantRefBase.hpp index 5a4c59beb..ad2817fe3 100644 --- a/src/ArduinoJson/Variant/VariantRefBase.hpp +++ b/src/ArduinoJson/Variant/VariantRefBase.hpp @@ -76,13 +76,16 @@ class VariantRefBase : public VariantTag { // Copies the specified value. // https://arduinojson.org/v7/api/jsonvariant/set/ template - bool set(const T& value) const { - return doSet>>(value); + bool set(T&& value) const { + using TypeForConverter = conditional_t::value, T, + remove_cv_t>>; + return doSet>(value); } // Copies the specified value. // https://arduinojson.org/v7/api/jsonvariant/set/ - template + template ::value>> bool set(T* value) const { return doSet>(value); } @@ -116,14 +119,14 @@ class VariantRefBase : public VariantTag { // Appends a value to the array. // https://arduinojson.org/v7/api/jsonvariant/add/ template - bool add(const T& value) const { - return detail::VariantData::addValue(getOrCreateData(), value, - getResourceManager()); + bool add(T&& value) const { + return detail::VariantData::addValue( + getOrCreateData(), detail::forward(value), getResourceManager()); } // Appends a value to the array. // https://arduinojson.org/v7/api/jsonvariant/add/ - template + template ::value>> bool add(T* value) const { return detail::VariantData::addValue(getOrCreateData(), value, getResourceManager()); @@ -190,12 +193,12 @@ class VariantRefBase : public VariantTag { template FORCE_INLINE enable_if_t::value, MemberProxy>> - operator[](const TString& key) const; + operator[](TString&& key) const; // Gets or sets an object member. // https://arduinojson.org/v7/api/jsonvariant/subscript/ template - FORCE_INLINE enable_if_t::value, + FORCE_INLINE enable_if_t::value && !is_const::value, MemberProxy>> operator[](TChar* key) const; diff --git a/src/ArduinoJson/Variant/VariantRefBaseImpl.hpp b/src/ArduinoJson/Variant/VariantRefBaseImpl.hpp index 1a0685f3b..210677fb0 100644 --- a/src/ArduinoJson/Variant/VariantRefBaseImpl.hpp +++ b/src/ArduinoJson/Variant/VariantRefBaseImpl.hpp @@ -124,7 +124,7 @@ inline ElementProxy VariantRefBase::operator[]( template template -inline enable_if_t::value, +inline enable_if_t::value && !is_const::value, MemberProxy>> VariantRefBase::operator[](TChar* key) const { return {derived(), adaptString(key)}; @@ -134,14 +134,14 @@ template template inline enable_if_t::value, MemberProxy>> -VariantRefBase::operator[](const TString& key) const { - return {derived(), adaptString(key)}; +VariantRefBase::operator[](TString&& key) const { + return {derived(), adaptString(detail::forward(key))}; } template template inline bool VariantRefBase::doSet(T&& value, false_type) const { - TConverter::toJson(value, getOrCreateVariant()); + TConverter::toJson(detail::forward(value), getOrCreateVariant()); auto resources = getResourceManager(); return resources && !resources->overflowed(); }