Compile error for json in template and variadic macros.
I'm now using Template and Variadic macros for converting user-defined class to json and output to ostream i.e. using in log and output current variables. And Boost log is integrated to record the variables into the log when running.
- Variadic Macros is used to define to_json function for user-defined classes;
#define MY_SERIALIZATION( class_name, ... ) \
public: \
void to_json( nlohmann::json& j ) { \
mySerialization( j, __VA_ARGS__ ); \
}; \
friend inline ostream& operator << ( ostream& os, class_name& obj ) { \
nlohmann::json j; \
obj.to_json( j ); \
os << j; \
return( os ); \
}
#define CLASS_PROPERTY( class_name, type_name, variable_name ) \
protected: \
type_name m_##variable_name; \
public: \
inline class_name& Set_##variable_name( type_name other){ \
m_##variable_name = other; return(*this); }; \
inline type_name& Get_##variable_name(void) { return( m_##variable_name ); }
- Define iteration functions for variadic macros.
template< class First >
void mySerialization( nlohmann::json& j, First first ) {
j.push_back( first );
}
template< class First, class ...Rest >
void mySerialization( nlohmann::json& j, First first, Rest ...rest ) {
j.push_back( first );
mySerialization( j, rest... );
}
template< class now >
void to_json( nlohmann::json& j, now& obj ) {
obj.to_json( j );
}
- Define user-defined class.
class CBase {
public:
CBase( int ii, long ll, float ff, double dd, string ss );
CLASS_PROPERTY( CBase, int, ii );
CLASS_PROPERTY( CBase, long, ll );
CLASS_PROPERTY( CBase, float, ff );
CLASS_PROPERTY( CBase, double, dd );
CLASS_PROPERTY( CBase, string, ss );
MY_SERIALIZATION( CBase, m_ii, m_ll, m_ff, m_dd, m_ss );
};
CBase::CBase( int ii=11, long ll=12, float ff=3.33, double dd=4.44, string ss="ss")
{
m_ii = ii;
m_ll = ll;
m_ff = ff;
m_dd = dd;
m_ss = ss;
};
class CNew1 {
public:
CLASS_PROPERTY( CNew1, CBase, objBase );
MY_SERIALIZATION( CNew1, m_objBase );
};
- Define source code to run the example.
int main(void)
{
INIT_LOG( "log", "IVS", 40, 200, debug );
int ii=10;
int& jj = ii;
DEBUG_LOG( "LOG for Debug", 11, 22, ii, jj );
INFO_LOG( "LOG for Info", 33, 44, ii, jj );
WARN_LOG( "LOG for Warn", 55, 66, ii, jj );
ERROR_LOG( "LOG for Error", 77, 88, ii, jj );
FATAL_LOG( "LOG for Fatal", 99, 100, ii, jj );
CBase objBase( 11, 22, 33.0, 44.0, "aa" );
ERROR_LOG( ii, jj, objBase );
CNew1 objNew1;
objNew1.Set_objBase( objBase );
ERROR_LOG( ii, jj, objNew1 );
- Compile Status: 5.1 The source code is compiled and run OK when all source code related CNew1 is commented. This means CBase class is OK for the source code. 5.2 There is compile error when CNew1 class is active.
mylog.h: In instantiation of ‘void mySerialization(nlohmann::json&, First) [with First = CBase; nlohmann::json = nlohmann::basic_json<>]’:
mylog.cpp:42:2: required from here
mylog.h:100:2: error: no matching function for call to ‘nlohmann::basic_json<>::push_back(CBase&)’
100 | j.push_back( first );
| ^
- I try to change to other convert CBase to Json, i.e. nlohmann::json jTmp = first; but not work.
What is the issue you have?
There is compile error when CNew1 is active, error info list upper.
The CBase can convert to json when CNew1 is commented, while not OK when CNew1 is active.
Please describe the steps to reproduce the issue.
- There are 3 file, mylog.hpp, mylog.cpp, aa.sh(compile the code);
- The environment is Ubuntu 20.04 LTS, with default gcc(9.3.0), libboost-dev(1.7.1 using for LOG), nlohmann::json(7.3.0) all software can get by apt-get install build-essential, libboost-all-dev, nlohmann;
- Source code of mylog.hpp
#ifndef MY_LOG_H
#define MY_LOG_H
#include <boost/log/common.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup.hpp>
#include <boost/log/support/date_time.hpp>
#include <nlohmann/json.hpp>
#include <sstream>
#include <iostream>
extern boost::log::sources::severity_logger_mt< boost::log::trivial::severity_level > gLogger;
#define INIT_LOG(fileDir, fileName, fileSize, maxFile, level) \
{ \
using namespace boost::log; \
auto formatter = ( expressions::stream \
<< "[" << expressions::format_date_time< boost::posix_time::ptime > \
( "TimeStamp", "%Y%m%d%H%M%S") \
<< "][" << expressions::attr<aux::thread::id>( "ThreadID") \
<< "][" << expressions::attr<unsigned int>( "LineID") \
<< "][" << trivial::severity \
<< "]" << expressions::smessage ); \
add_common_attributes(); \
core::get()->add_sink( add_file_log( \
keywords::file_name = fileName"_%N.log", \
keywords::target = fileDir, \
keywords::open_mode = std::ios::app, \
keywords::rotation_size = fileSize * 1024 * 1024, \
keywords::max_files = maxFile, \
keywords::auto_flush = true, \
keywords::scan_method = sinks::file::scan_matching, \
keywords::format = formatter \
) ); \
core::get()->add_sink( add_file_log( \
keywords::file_name = fileName"_%N.err", \
keywords::target = fileDir, \
keywords::open_mode = std::ios::app, \
keywords::rotation_size = fileSize * 1024 * 1024, \
keywords::max_files = maxFile, \
keywords::auto_flush = true, \
keywords::scan_method = sinks::file::scan_matching, \
keywords::filter = trivial::severity >= trivial::warning, \
keywords::format = formatter \
) ); \
core::get()->set_filter( trivial::severity >= trivial::level ); \
}
#define MY_LOG(level, ...) myLog( level, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__ )
#define TRACE_LOG(...) MY_LOG(boost::log::trivial::trace, __VA_ARGS__)
#define DEBUG_LOG(...) MY_LOG(boost::log::trivial::debug, __VA_ARGS__)
#define INFO_LOG(...) MY_LOG(boost::log::trivial::info, __VA_ARGS__)
#define WARN_LOG(...) MY_LOG(boost::log::trivial::warning, __VA_ARGS__)
#define ERROR_LOG(...) MY_LOG(boost::log::trivial::error, __VA_ARGS__)
#define FATAL_LOG(...) MY_LOG(boost::log::trivial::fatal, __VA_ARGS__)
template<class Last1, class Last2>
void myString( std::ostringstream& oss, Last1&& last1, Last2&& last2 ) {
oss << last1 << ":" << last2;
}
template<class First, class Second, class ...Rest>
void myString( std::ostringstream& oss, First&& first, Second&& second, Rest&& ...rest ) {
oss << first << ":" << second;
myString( oss, rest... );
}
template<class First, class ...Rest>
void myLog( boost::log::trivial::severity_level level, First&& first, Rest&& ...rest ) {
std::ostringstream oss;
myString( oss, first, rest... );
BOOST_LOG_SEV( gLogger, level ) << oss.str();
}
#define CLASS_PROPERTY( class_name, type_name, variable_name ) \
protected: \
type_name m_##variable_name; \
public: \
inline class_name& Set_##variable_name( type_name other){ \
m_##variable_name = other; return(*this); }; \
inline type_name& Get_##variable_name(void) { return( m_##variable_name ); }
#define MY_SERIALIZATION( class_name, ... ) \
public: \
void to_json( nlohmann::json& j ) { \
mySerialization( j, __VA_ARGS__ ); \
}; \
friend inline ostream& operator << ( ostream& os, class_name& obj ) { \
nlohmann::json j; \
obj.to_json( j ); \
os << j; \
return( os ); \
}
template< class First >
void mySerialization( nlohmann::json& j, First first ) {
j.push_back( first );
}
template< class First, class ...Rest >
void mySerialization( nlohmann::json& j, First first, Rest ...rest ) {
j.push_back( first );
mySerialization( j, rest... );
}
template< class now >
void to_json( nlohmann::json& j, now& obj ) {
obj.to_json( j );
}
#endif // MY_LOG_H
- Content for mylog.cpp
#include <iostream>
#include <string>
#include <vector>
#include "mylog.h"
using namespace std;
boost::log::sources::severity_logger_mt< boost::log::trivial::severity_level > gLogger;
class CBase {
public:
CBase( int ii, long ll, float ff, double dd, string ss );
CLASS_PROPERTY( CBase, int, ii );
CLASS_PROPERTY( CBase, long, ll );
CLASS_PROPERTY( CBase, float, ff );
CLASS_PROPERTY( CBase, double, dd );
CLASS_PROPERTY( CBase, string, ss );
MY_SERIALIZATION( CBase, m_ii, m_ll, m_ff, m_dd, m_ss );
};
CBase::CBase( int ii=11, long ll=12, float ff=3.33, double dd=4.44, string ss="ss")
{
m_ii = ii;
m_ll = ll;
m_ff = ff;
m_dd = dd;
m_ss = ss;
};
class CNew1 {
public:
CLASS_PROPERTY( CNew1, CBase, objBase );
MY_SERIALIZATION( CNew1, m_objBase );
};
/*
class CNew {
public:
CLASS_PROPERTY( CNew, vector<CBase>, vecBase );
MY_SERIALIZATION( CNew, m_vecBase );
};
*/
int main(void)
{
INIT_LOG( "log", "IVS", 40, 200, debug );
int ii=10;
int& jj = ii;
DEBUG_LOG( "LOG for Debug", 11, 22, ii, jj );
INFO_LOG( "LOG for Info", 33, 44, ii, jj );
WARN_LOG( "LOG for Warn", 55, 66, ii, jj );
ERROR_LOG( "LOG for Error", 77, 88, ii, jj );
FATAL_LOG( "LOG for Fatal", 99, 100, ii, jj );
CBase objBase( 11, 22, 33.0, 44.0, "aa" );
ERROR_LOG( ii, jj, objBase );
CNew1 objNew1;
objNew1.Set_objBase( objBase );
ERROR_LOG( ii, jj, objNew1 );
/*
CNew objNew;
objNew.Get_vecBase().push_back( objBase );
objNew.Get_vecBase().push_back( objBase );
ERROR_LOG( ii, jj, objNew );
*/
/*
vector<CNew> vectorNew;
vectorNew.push_back( objNew );
vectorNew.push_back( objNew );
vectorNew.push_back( objNew );
cout << vectorNew << endl;
*/
}
- Content for aa.sh which is defined to compile the source code.
g++ -std=c++17 -DBOOST_LOG_DYN_LINK -o mylog mylog.cpp -lboost_date_time -lboost_filesystem -lboost_thread -lboost_log_setup -lboost_log -lpthread -frtti -fexceptions
Can you provide a small but working code example?
Yes, the source code are list upper.
What is the expected behavior?
- There will be a log directory, and the log data will be recorded in the files under the log directory.
- If comment the source code of CNew1, the program can be compiled and run OK.
- If uncomment the source code of CNew1, then there is a compile error.
And what is the actual behavior instead?
There is a compile error and I just list the first error message upper.
Which compiler and operating system are you using?
- Compiler: gcc 9.3.0_
- Operating system: Ubuntu 20.04 LTS_
Which version of the library did you use?
- [ ] latest release version 3.9.1
- [x] other release - please state the version: 3.7.3_
- [ ] the
developbranch
If you experience a compilation error: can you compile and run the unit tests?
- [ ] yes
- [ ] no - please copy/paste the error message below The nlohmann::json is running OK when only CBase class is tested, so, I don't think nlohmann:json has compile error.
Can you please try with the latest release version or the develop branch?
Thanks for your reply.
- No difference between JSON 3.7.3 and the latest version of 3.9.1 for the upper problem and its source code.
- I think it is the problem of mixing macro and template, and which may depend on the compile sequence, but it's very hard to check why.
- I just implementation the logic using boost::preprocessor, and I list my code to you for reference:
- The following source code can run with JSON 3.7.3.
#include <boost/preprocessor.hpp>
#include <nlohmann/json.hpp>
#include <string>
#include <vector>
#include <iostream>
using namespace std;
#define MY_CLASS_PROPERTY( _class, _type, _var ) \
private: \
_type BOOST_PP_CAT( m_, _var ); \
public: \
inline _type& BOOST_PP_CAT( Get_, _var )( void ) { return( BOOST_PP_CAT( m_, _var ) ); }; \
inline _class& BOOST_PP_CAT( Set_, _var )( _type& other ){ BOOST_PP_CAT( m_, _var ) = other; return( *this ); };
#define MY_GET_CLASS_PROPERTY( _z, _n, _seq ) \
BOOST_PP_EXPR_IF( BOOST_PP_NOT( BOOST_PP_MOD( BOOST_PP_ADD( _n, 1 ), 2 ) ), \
MY_CLASS_PROPERTY( BOOST_PP_SEQ_ELEM( 0, _seq ), BOOST_PP_SEQ_ELEM( _n, _seq ), \
BOOST_PP_SEQ_ELEM( BOOST_PP_ADD( _n, 1 ), _seq ) ) )
#define MY_ALL_CLASS_PROPERTY( _seq ) \
BOOST_PP_REPEAT( BOOST_PP_SUB( BOOST_PP_SEQ_SIZE( _seq ), 1 ), MY_GET_CLASS_PROPERTY, _seq )
#define MY_JSON_VARIABLE( var_name ) _j[ BOOST_PP_STRINGIZE(var_name) ] = _t.var_name;
#define MY_GET_JSON( _z, _n, _seq ) \
BOOST_PP_EXPR_IF( BOOST_PP_AND( BOOST_PP_NOT( BOOST_PP_MOD( _n, 2) ), BOOST_PP_GREATER( _n, 0 ) ), \
MY_JSON_VARIABLE( BOOST_PP_CAT( m_, BOOST_PP_SEQ_ELEM( _n, _seq ) ) ) )
#define MY_JSON_MACRO( _seq ) \
public: \
friend void to_json(nlohmann::json& _j, const BOOST_PP_SEQ_ELEM( 0, _seq )& _t) { \
BOOST_PP_REPEAT( BOOST_PP_SEQ_SIZE( _seq ), MY_GET_JSON, _seq ) \
};
#define MY_CLASS_DEFINE( class_name, ... ) \
MY_ALL_CLASS_PROPERTY( BOOST_PP_SEQ_PUSH_FRONT( BOOST_PP_VARIADIC_TO_SEQ( __VA_ARGS__ ), class_name ) ) \
MY_JSON_MACRO( BOOST_PP_SEQ_PUSH_FRONT( BOOST_PP_VARIADIC_TO_SEQ( __VA_ARGS__ ), class_name ) )
class CBase {
public:
inline CBase( int ii=1, float ff=2.2, string ss="3.33" ) { m_ii = ii; m_ff = ff; m_ss = ss; };
MY_CLASS_DEFINE( CBase, int, ii, float, ff, string, ss )
};
class CComplex {
MY_CLASS_DEFINE( CComplex, vector<CBase>, vectorBase );
};
int main(void)
{
CBase objBase;
cout << nlohmann::json(objBase) << endl;
CComplex objComplex;
objComplex.Get_vectorBase().push_back( objBase );
objComplex.Get_vectorBase().push_back( objBase );
cout << nlohmann::json( objComplex ) << endl;
}
~
- Follow is the expanded source code for the macro, which are work as expected.
class CBase {
public:
inline CBase( int ii=1, float ff=2.2, string ss="3.33" ) { m_ii = ii; m_ff = ff; m_ss = ss; };
private: int m_ii; public: inline int& Get_ii( void ) { return( m_ii ); }; inline CBase& Set_ii( int& other ){ m_ii = other; return( *this ); }; private: float m_ff; public: inline float& Get_ff( void ) { return( m_ff ); }; inline CBase& Set_ff( float& other ){ m_ff = other; return( *this ); }; private: string m_ss; public: inline string& Get_ss( void ) { return( m_ss ); }; inline CBase& Set_ss( string& other ){ m_ss = other; return( *this ); }; public: friend void to_json(nlohmann::json& _j, const CBase& _t) { _j[ "m_ii" ] = _t.m_ii; _j[ "m_ff" ] = _t.m_ff; _j[ "m_ss" ] = _t.m_ss; };
};
class CComplex {
private: vector<CBase> m_vectorBase; public: inline vector<CBase>& Get_vectorBase( void ) { return( m_vectorBase ); }; inline CComplex& Set_vectorBase( vector<CBase>& other ){ m_vectorBase = other; return( *this ); }; public: friend void to_json(nlohmann::json& _j, const CComplex& _t) { _j[ "m_vectorBase" ] = _t.m_vectorBase; };;
};
I wasn't able to compile your example easily. Can you come up with a minimal example that doesn't involve linking to external libraries?
I think (part of) your issue is that this function:
template <class now>
void to_json(nlohmann::json& j, now& obj)
{
obj.to_json(j);
}
Is completely overriding all the default serializers e.g. for std::vector. So it's trying to serialize a std::vector<CBase> with this function instead of nlohmann's, which tries to do vector.to_json(j), which doesn't exist.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.