NLOHMANN_DEFINE_TYPE_* fails with zero members
Description
I found it annoying when working with multiple tiny polymorphic classes, trying to provide identical interface for each of them, that I can't use the macro for a class that has no members to (de)serialize.
Reproduction steps
Use (probably) any of the NLOHMANN_DEFINE_TYPE_* macros with only first argument and no members.
F.e. NLOHMANN_DEFINE_TYPE_INTRUSIVE(MyClass)
Expected vs. actual results
Expected:
friend void to_json(nlohmann::json&, const MyClass&) {}
friend void from_json(const nlohmann::json&, MyClass&) {}
Actual:
friend void to_json(nlohmann::json& nlohmann_json_j, const MyClass& nlohmann_json_t) {
nlohmann_json_j[] = nlohmann_json_t.;
}
friend void from_json(const nlohmann::json& nlohmann_json_j, MyClass& nlohmann_json_t) {
nlohmann_json_j.at().get_to(nlohmann_json_t.);
}
Minimal code example
class MyClass
{
NLOHMANN_DEFINE_TYPE_INTRUSIVE(MyClass);
};
Error messages
Error C2661 'nlohmann::json_abi_v3_11_2::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_11_2::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>>::at': no overloaded function takes 0 arguments
Error C2661 'nlohmann::json_abi_v3_11_2::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_11_2::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>>::at': no overloaded function takes 0 arguments
Error C2661 'nlohmann::json_abi_v3_11_2::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_11_2::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>>::at': no overloaded function takes 0 arguments
Error C2661 'nlohmann::json_abi_v3_11_2::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_11_2::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>>::at': no overloaded function takes 0 arguments
Error C2059 syntax error: ')'
Error C2059 syntax error: ')'
Error C2059 syntax error: ')'
Error C2059 syntax error: ')'
Error C2059 syntax error: ']'
Error C2059 syntax error: ']'
Error C2059 syntax error: ']'
Error C2059 syntax error: ']'
Compiler and operating system
Microsoft Visual C++ 2022
Library version
3.11.2
Validation
- [ ] The bug also occurs if the latest version from the
developbranch is used. - [ ] I can successfully compile and run the unit tests.
In case someone was interested in how to proceed with fixing this issue:
This bug is caused by the way the macro functional meta programming is implemented for this project and by a standard behaviour of C preprocessor's __VA_ARGS__ that expands as an empty token when no arguments are passed to a variadic macro.
Due to this property of __VA_ARGS__, for example, #define M(...) __VA_ARGS__ would be expanded to an empty token for M()).
So, for instance, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(MyStruct) leads to an expansion, that contains NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, ), thus expanding NLOHMANN_JSON_TO().
See https://github.com/nlohmann/json/blob/a0c1318830519eac027a31edec1a99ce1ae5670e/include/nlohmann/detail/macro_scope.hpp#L406 for more details.
There are several ways how to tackle with this problem:
-
Using non-standard comma swallow extension (GNU)
By replacing
, __VA_ARGS__with, ## __VA_ARGS__, this effectively discards,in case of empty arguments. Unfortunately, only supported by gcc/clang/msvc and maybe other compilers . See the last paragraph of https://gcc.gnu.org/onlinedocs/gcc/Variadic-Macros.html - Using some standard, yet complicated macro property to avoid use of the extension mentioned above See https://stackoverflow.com/a/8445641
Alternatively, just add another macro, eg. _EMPTY which only takes the type argument. But I guess it's not ideal.
In the beginning I thought "Just add an overload with 1 arg". And then I understood, yeah, macros are not functions 😩
It can be easily fixed with __VA_OPT__
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \
friend void to_json(nlohmann::json& __VA_OPT__(nlohmann_json_j), const Type& __VA_OPT__(nlohmann_json_t)) { __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__))) } \
friend void from_json(const nlohmann::json& __VA_OPT__(nlohmann_json_j), Type& __VA_OPT__(nlohmann_json_t)) { __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__))) }
But __VA_OPT__ requires c++20. I guess that also can be circumvented with
#ifndef JSON_HAS_CPP_20
#define __VA_OPT__(x) x
#endif
but that still won't fix the bug for the earlier versions.