Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NLOHMANN_DEFINE_TYPE_* fails with zero members #4041

Open
2 tasks
dawinaj opened this issue May 23, 2023 · 4 comments
Open
2 tasks

NLOHMANN_DEFINE_TYPE_* fails with zero members #4041

dawinaj opened this issue May 23, 2023 · 4 comments

Comments

@dawinaj
Copy link

dawinaj commented May 23, 2023

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

@Romop5
Copy link

Romop5 commented Jun 3, 2023

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

#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \
for more details.

There are several ways how to tackle with this problem:

  1. 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
  2. Using some standard, yet complicated macro property to avoid use of the extension mentioned above
    See https://stackoverflow.com/a/8445641

@dawinaj
Copy link
Author

dawinaj commented Jun 4, 2023

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 😩

@radistmorse
Copy link

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.

@nlohmann
Copy link
Owner

Should be fixed by #4323.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants