📜 ⬆️ ⬇️

My bike to reflection in c ++

Inspired by the publication of “Logger with member functions that are not there,” I decided to give out for everyone to see my meta-bike for reflection, which I use quite successfully, and not only for logging. But let's start with simple logging, continuing the above publication.

When implementing logging, tasks for yourself were set as follows:


The frontend looks like this:
')
1. In the constructor CConnection we logged:
TestLog::Log<LOG_NOTICE>() << *this << "connection created"; 

2. Where CConnection is inherited from
 public TLogHeader<'c', CConnection> 

3. Who, using CRTP , knows that CConnection has this:
  using this_t = CConnection; int m_id; using m_id_t = TParamAccessor<this_t, decltype(this_t::m_id), &this_t::m_id>; std::string m_name; using m_name_t = TParamAccessor<this_t, decltype(this_t::m_name), &this_t::m_name>; char m_state; using m_state_t = TParamAccessor<this_t, decltype(this_t::m_state), &this_t::m_state>; using log_header_param_list_t = std::tuple<m_id_t, m_name_t, m_state_t>; 

4. And turns this data into a log line:
 c:23:test_conn 1:a:connection created 

Where a colon is listed: c - the first parameter of the TLogHeader template, and then the values ​​of m_id, m_name, m_state

In my case, all this is further written to rsyslog and there it is parsed into the appropriate fields for MongoDB (depending on the type of header, that is, the first parameter before the colon).

Now this approach is in my production on a high-load system, so any constructive stones will be in peak, or better under cat. And if laziness, here's a working example at http://coliru.stacked-crooked.com/ under the code cut of 250 lines, but a lot of letters in the description.

Sorry if there is a lot of transliteration and English terms. The article is a complete gag, but for 20 years it somehow lagged behind Russian terminology. Therefore, there will be no references to the literature, although at one point I was helped on stackoverflow.com, but giving a link there means burning my anonymity as a sand user, as I understand it.

So.

Part 1 - Logging


Start simple

 TestLog::Log<LOG_NOTICE>() << *this << "connection created"; 


“Template TestLog :: Log” in our case is a type (but can also be implemented as a function that returns a type). As we see, a temporary object of this type is created. And, knowing that any object will be destroyed when leaving the scope, it is clear that for this type it will be ';'. Accordingly, the behavior model is simple - we merge the data into this type, after which the destructor is called.

It is implemented in the class TSink
 template<int logLevel, bool isNull> struct TSink { ~TSink() { std::cerr << "std::err log line flushed: \"" << stream.str() << " \"" << std::endl; } std::stringstream stream; template<typename T> TSink<logLevel, isNull>& operator << (T&& val) { stream << val; return *this; } }; template<int logLevel> struct TSink<logLevel, true> { template<typename T> TSink<logLevel, true>& operator << (T&& val) { return *this; } }; 

Partial specialization is needed here in order to “Have the ability to turn off unnecessary logging levels at the compilation stage with one constant; for example: everything above LOG_NOTICE should not fall into the resulting binary. ” This is a gift to us from the compiler (which I’m not 100% sure of, since I’m not too eager to decompile the code and see what we have at the exit; hello, stones!). The idea is simple: the write statement to the stream does not perform any data manipulations, and if we take into account that we have created a temporary object and everything we do before deleting it, we write it into the stream, then the modern compiler optimizes this whole thing.

And this is how it will look like if instead of cerr we are merging into syslog
 template<int logLevel, bool isNull> struct TSink { ~TSink() { syslog(logLevel, stream.str().c_str(), "junk"); } std::stringstream stream; template<typename T> TSink<logLevel, isNull>& operator << (T&& val) { stream << val; return *this; } }; template<int logLevel> struct TSink<logLevel, true> { template<typename T> TSink<logLevel, true>& operator << (T&& val) { return *this; } }; 

It is worth mentioning that TSink is already the final implementation tied to a specific logging module (for example: standard output stream, file, syslog, etc.). The TLog class itself, which we will look at later, does not know how it will be implemented, it receives traits at the input, which will already refer to a specific implementation of the sink.

Let's see what TestLog :: Log is

It:
 static const int OUR_LOG_LEVEL = LOG_DEBUG; using TestLog = TLog<OUR_LOG_LEVEL, TLogTraitsStdErr>; 

And, actually, implementation of TLog
 template<int logUpTo, template<int> class log_traits> struct TLog : public log_traits<logUpTo> { using trats_t = log_traits<logUpTo>; template<int logLevel, bool isNull> using sink_type = typename trats_t::template sink_type<logLevel, isNull>; template<int neededLogLevel, int logLevel> struct TGetLogOfLevel { using type = typename std::conditional< neededLogLevel == logLevel, sink_type<neededLogLevel, (neededLogLevel > logUpTo)>, typename TGetLogOfLevel<neededLogLevel, logLevel + 1>::type >::type; }; template<int neededLogLevel> struct TGetLogOfLevel<neededLogLevel, LOG_DEBUG + 1> { using type = void; }; template<int neededLogLevel> using Log = typename TGetLogOfLevel<neededLogLevel, 0>::type; }; 

We get two parameters to the “template input”: the maximum logging level in this program assembly (OUR_LOG_LEVEL) and the log_traits template type, from which we inherit from hereafter.

As can be seen from the code, the log_traits type has two requirements:

It all works very simply inside: we define the type Log, which is the result of a call to the recursive meta-function TGetLogOfLevel (neededLogLevel, logLevel), which in the case of usedLogLevel == logLevel returns the current level's sink_type or recursively calls itself with a logging level of +1.

Based on all this, we will do the final log_traits implementation

To do this, we wrap our ready-made TSink into a new TLogTraits class considering the requirements for log_traits described above. Those. define the type of traits_type as
 template<int logLevel, bool isNull> using sink_type = TSink<logLevel, isNull>; 
We also add the OpenLog function and close it in the (log) destructor.

Here is the code for TLogTraitsStdErr
 template<int logUpTo> struct TLogTraitsStdErr { void Open(const char* logName, int facility) { std::cerr << "std::err log opened with logName: " << logName << std::endl; } ~TLogTraitsStdErr() { std::cerr << "std::err log closed" << std::endl; } template<int logLevel, bool isNull> struct TSink { ~TSink() { std::cerr << "std::err log line flushed: \"" << stream.str() << " \"" << std::endl; } std::stringstream stream; template<typename T> TSink<logLevel, isNull>& operator << (T&& val) { stream << val; return *this; } }; template<int logLevel> struct TSink<logLevel, true> { template<typename T> TSink<logLevel, true>& operator << (T&& val) { return *this; } }; template<int logLevel, bool isNull> using sink_type = TSink<logLevel, isNull>; }; 

And by analogy TLogTraitsSyslog
 template<int logUpTo> struct TLogTraitsSyslog { static void Open(const char* logName, int facility) { openlog(logName, LOG_PID | LOG_NDELAY, facility); setlogmask(LOG_UPTO(logUpTo)); } ~TLogTraitsSyslog() { closelog(); } template<int logLevel, bool isNull> struct TSink { ~TSink() { syslog(logLevel, stream.str().c_str(), "junk"); } std::stringstream stream; template<typename T> TSink<logLevel, isNull>& operator << (T&& val) { stream << val; return *this; } }; template<int logLevel> struct TSink<logLevel, true> { template<typename T> TSink<logLevel, true>& operator << (T&& val) { return *this; } }; template<int logLevel, bool isNull> using sink_type = TSink<logLevel, isNull>; }; 

And, of course, we will test this cotovasia

Write testcase
 static const int OUR_LOG_LEVEL = LOG_DEBUG; using TestLog = TLog<OUR_LOG_LEVEL, TLogTraitsStdErr>; struct CConnection { CConnection() { TestLog::Log<LOG_NOTICE>() << "connection created"; } ~CConnection() { TestLog::Log<LOG_NOTICE>() << "connection destroyed"; } void DoSomething() { TestLog::Log<LOG_DEBUG>() << "connection is doing something"; } }; class CServer : public TestLog { public: CServer() { TestLog::Open("test_log", 1); }; int Run() { TestLog::Log<LOG_DEBUG>() << "Creating connection"; CConnection test_conn1; test_conn1.DoSomething(); return 0; } }; int main(int argc, char** argv) { CServer server; return server.Run(); } 

Compile and get this output:
 clang++ -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out std::err log opened with logName: test_log std::err log line flushed: "Creating connection " std::err log line flushed: "connection created " std::err log line flushed: "connection is doing something " std::err log line flushed: "connection destroyed " std::err log closed 

And we will play with him on (I do not even know how to call this resource) Coliru

As a result, we got 25 lines of the TLog meta-class, and ~ 40 for each specific implementation of log_traits. And the requirements are fulfilled: a statement that turns off unnecessary logging is done; simply? - like yes; transparent? - if you read the meta code is not difficult.
Kiss from my point of view.
How about you? Hi stones 2!

Part 2 - Reflection


Expand the CConnection in our testcase

Remember, at the beginning there were interesting buns:
TLogHeader <'c', CConnection>, using CRTP , knows that CConnection has this:
  using this_t = CConnection; int m_id; using m_id_t = TParamAccessor<this_t, decltype(this_t::m_id), &this_t::m_id>; std::string m_name; using m_name_t = TParamAccessor<this_t, decltype(this_t::m_name), &this_t::m_name>; char m_state; using m_state_t = TParamAccessor<this_t, decltype(this_t::m_state), &this_t::m_state>; using log_header_param_list_t = std::tuple<m_id_t, m_name_t, m_state_t>; 

Here is our advanced CConnection
 struct CConnection : public TLogHeader<'c', CConnection> { CConnection(int _id, const std::string& _name) : m_id(_id), m_name(_name), m_state('a') { TestLog::Log<LOG_NOTICE>() << *this << "connection created"; m_state = 'b'; } ~CConnection() { TestLog::Log<LOG_NOTICE>() << *this << "connection destroyed"; } void DoSomething() { TestLog::Log<LOG_DEBUG>() << *this << "connection is doing something"; m_state = 'c'; } using this_t = CConnection; int m_id; using m_id_t = TParamAccessor<this_t, decltype(this_t::m_id), &this_t::m_id>; std::string m_name; using m_name_t = TParamAccessor<this_t, decltype(this_t::m_name), &this_t::m_name>; char m_state; using m_state_t = TParamAccessor<this_t, decltype(this_t::m_state), &this_t::m_state>; using log_header_param_list_t = std::tuple<m_id_t, m_name_t, m_state_t>; }; 

Of course, all this can be wrapped in a simple macro, such as PARAM (int, m_id), but we will not for the sanity sake.

TParamAccessor is a type in which one static GetRef function is defined, which returns a reference to a class variable.
So here
 struct TParamAccessorDefaultTraits { }; template <typename _object_t, typename _value_type, _value_type _object_t::*member_t, typename _traits_t = TParamAccessorDefaultTraits> struct TParamAccessor : public _traits_t { using traits_t = _traits_t; using object_t = _object_t; using value_type = _value_type; static value_type& GetRef(object_t& data) { return data.*member_t; } }; 
Accordingly, in order for us to stuff * this into the sink_type stream, we need to inherit from TLogHeader <'c', CConnection> to iterate inside it by CConnection :: log_header_param_list_t and call sink_type << TParamAccessor :: GetRef (our object).

Warning: it will be a little difficult to understand here until we look at the meta version of for_each

We get the class TLogHeader
 template<char moduleName, typename object_t> struct TLogHeader { template<size_t idx, typename accessor_t> struct TLogHeaderWriter { using type = TLogHeaderWriter<idx, accessor_t>; static void call(typename accessor_t::object_t& obj, std::stringstream& out) { typename accessor_t::value_type& val = accessor_t::GetRef(obj); out << val << ":"; } }; template<typename sink_type> friend sink_type& operator << (sink_type& sink, object_t& val) { std::stringstream header; using writers = typename for_each<typename object_t::log_header_param_list_t>::template instantiate<TLogHeaderWriter>; for_each<writers>::call(*static_cast<object_t*>(&val), header); sink << moduleName << ":" << header.str(); return sink; } }; 

TLogHeader describes only one function - an overloaded write statement to a stream.
In which we see for_each in two ways:
  1. a meta-function call to determine the type of writers to which all TLogHeaderWriter instances <from object_t :: log_header_param_list_t :: from every type inside> are returned as a tuple;)
  2. call the static call function for all types defined in writers

Make a "meta-hypostasis" for_each

To do this, simplify our TLogHeader before calling only the meta-function.
 template<char moduleName, typename object_t> struct TLogHeader { template<size_t idx, typename accessor_t> struct TLogHeaderWriter { using type = TLogHeaderWriter<idx, accessor_t>; }; template<typename sink_type> friend sink_type& operator << (sink_type& sink, object_t& val) { using writers = typename for_each<typename object_t::log_header_param_list_t>::template instantiate<TLogHeaderWriter>; return sink; } }; 

And write the meta function.
Disclaimer: because for simplicity, cut the final code, not tested; the final one can later be tested live.

Just in case, I will clarify: in the most pleasant terminology to me, “a meta-function is a function performed at the stage of compiling a program, and the result of which is a new type” (all in your own words)

I will comment on the code, because further a little confused will go.

For_each code
 //////////////////////////////////////////////////////////////// // append to tuple //-       new_t //    tuple    template argument pack,       template<typename new_t, typename... other_t> struct append_to_tuple { using type = std::tuple<other_t..., new_t>; }; template<typename new_t, typename... other_t> struct append_to_tuple<new_t, std::tuple<other_t...>> { using type = std::tuple<other_t..., new_t>; }; //////////////////////////////////////////////////////////////// // for_each_impl // for_each -  ,   func_t  instatiate, //,         tuple  append_to_tuple // ,  ,           template<size_t i, typename... args_t> struct for_each_impl { using this_type = typename std::tuple_element<i, std::tuple<args_t...>>::type; using prev_type = for_each_impl<i - 1, args_t...>; template<template<size_t, typename> class func_t> using instantiate = typename append_to_tuple< typename func_t<i, this_type>::type, typename prev_type::template instantiate<func_t> >::type; }; //       //       template<typename... args_t> struct for_each_impl<0, args_t...> { using this_type = typename std::tuple_element<0, std::tuple<args_t...>>::type; template<template<size_t, typename> class func_t> using instantiate = std::tuple<typename func_t<0, this_type>::type>; }; /////////////////////////////////////////////////////////////// // for_each // . //  for_each_impl,     // +     tuple< >  tuple<>   template<typename... args_t> struct for_each { using prev_type = for_each_impl<sizeof...(args_t) - 1, args_t...>; template<template<size_t, typename> class func_t> using instantiate = typename prev_type::template instantiate<func_t>; }; template<typename... args_t> struct for_each<std::tuple<args_t...>> : public for_each<args_t...> { }; template<> struct for_each<std::tuple<>> { template<template<size_t, typename> class func_t> using instantiate = std::tuple<>; }; 

Thus, we have obtained using writers = typename for_each <typename object_t :: log_header_param_list_t> :: template instantiate; where writers will unfold in:
 std::tuple< TLogHeaderWriter<0, TParamAccessor<CConnection, decltype(CConnection::m_id), &CConnection::m_id>>, TLogHeaderWriter<1, TParamAccessor<CConnection, decltype(CConnection::m_name), &CConnection::m_name>>, TLogHeaderWriter<2, TParamAccessor<CConnection, decltype(CConnection::m_state), &CConnection::m_state>> >; 

It remains to go through these types, and call GetRef with the subsequent entry into the stream.

Let's make a “run-time-hypostasis” for_each

And at once we will look at the extended for_each, to which the calls of the static call function are added (only it is worth paying attention to, the rest is unchanged)
And here it is - the last piece of code
 //////////////////////////////////////////////////////////////// // for_each_impl template<size_t i, typename... args_t> struct for_each_impl { using this_type = typename std::tuple_element<i, std::tuple<args_t...>>::type; using prev_type = for_each_impl<i - 1, args_t...>; template<template<size_t, typename> class func_t> using instantiate = typename append_to_tuple< typename func_t<i, this_type>::type, typename prev_type::template instantiate<func_t> >::type; template<typename object_t, typename... call_args_t> static void call(object_t&& obj, call_args_t&&... args) { prev_type::call(std::forward<object_t>(obj), std::forward<call_args_t>(args)...); this_type::call(std::forward<object_t>(obj), std::forward<call_args_t>(args)...); } }; template<typename... args_t> struct for_each_impl<0, args_t...> { using this_type = typename std::tuple_element<0, std::tuple<args_t...>>::type; template<template<size_t, typename> class func_t> using instantiate = std::tuple<typename func_t<0, this_type>::type>; template<typename object_t, typename... call_args_t> static void call(object_t&& obj, call_args_t&&... args) { this_type::call(std::forward<object_t>(obj), std::forward<call_args_t>(args)...); } template<typename object_t> static void call(object_t&& obj) { this_type::call(std::forward<object_t>(obj)); } }; /////////////////////////////////////////////////////////////// // for_each template<typename... args_t> struct for_each { using prev_type = for_each_impl<sizeof...(args_t) - 1, args_t...>; template<template<size_t, typename> class func_t> using instantiate = typename prev_type::template instantiate<func_t>; template<typename object_t, typename... call_args_t> static object_t call(object_t&& obj, call_args_t&&... args) { prev_type::call(std::forward<object_t>(obj), std::forward<call_args_t>(args)...); return obj; } template<typename object_t> static object_t call(object_t&& obj) { prev_type::call(std::forward<object_t>(obj)); return obj; } }; template<typename... args_t> struct for_each<std::tuple<args_t...>> : public for_each<args_t...> { }; template<> struct for_each<std::tuple<>> { template<template<size_t, typename> class func_t> using instantiate = std::tuple<>; template<typename object_t, typename... call_args_t> static object_t call(object_t&& obj, call_args_t&&... args) { return obj; } template<typename object_t> static object_t call(object_t&& obj) { return obj; } }; 

I think it’s not necessary to comment here: call receives at least one parameter on the input, which is our proxy object, and which is returned at the end of the work - the other parameters are forwarded to call calls.
In our case, this is TLogHeaderWirter :: call:
 static void call(typename accessor_t::object_t& obj, std::stringstream& out) { typename accessor_t::value_type& val = accessor_t::GetRef(obj); out << val << ":"; } 
Where accessor_t :: object_t = CConnection
And, of course, perfect forwarding is a thing!

Part 3 - Conclusion


All code in its entirety
 #include <cstdlib> #include <iostream> #include <sstream> #include <syslog.h> static const int OUR_LOG_LEVEL = LOG_DEBUG; // = LOG_NOTICE; // log up to LOG_DEBUG output: //std::err log opened with logName: test_log //std::err log line flushed: "s:1:test server:Creating connection " //std::err log line flushed: "c:23:test_conn 1:a:connection created " //std::err log line flushed: "c:23:test_conn 1:b:connection is doing something " //std::err log line flushed: "c:23:test_conn 1:c:connection destroyed " //std::err log closed // log up to LOG_NOTICE output: //std::err log opened with logName: test_log //std::err log line flushed: "c:23:test_conn 1:a:connection created " //std::err log line flushed: "c:23:test_conn 1:c:connection destroyed " //std::err log closed // ---------------------------- for_each.h start ----------------------------// #include <tuple> #include <utility> namespace helpers { //////////////////////////////////////////////////////////////// // param accessor struct TParamAccessorDefaultTraits { }; template <typename _object_t, typename _value_type, _value_type _object_t::*member_t, typename _traits_t = TParamAccessorDefaultTraits> struct TParamAccessor : public _traits_t { using traits_t = _traits_t; using object_t = _object_t; using value_type = _value_type; static value_type& GetRef(object_t& data) { return data.*member_t; } }; //////////////////////////////////////////////////////////////// // append to tuple template<typename new_t, typename... other_t> struct append_to_tuple { using type = std::tuple<other_t..., new_t>; }; template<typename new_t, typename... other_t> struct append_to_tuple<new_t, std::tuple<other_t...>> { using type = std::tuple<other_t..., new_t>; }; //////////////////////////////////////////////////////////////// // for_each_impl template<size_t i, typename... args_t> struct for_each_impl { using this_type = typename std::tuple_element<i, std::tuple<args_t...>>::type; using prev_type = for_each_impl<i - 1, args_t...>; template<template<size_t, typename> class func_t> using instantiate = typename append_to_tuple< typename func_t<i, this_type>::type, typename prev_type::template instantiate<func_t> >::type; template<typename object_t, typename... call_args_t> static void call(object_t&& obj, call_args_t&&... args) { prev_type::call(std::forward<object_t>(obj), std::forward<call_args_t>(args)...); this_type::call(std::forward<object_t>(obj), std::forward<call_args_t>(args)...); } }; template<typename... args_t> struct for_each_impl<0, args_t...> { using this_type = typename std::tuple_element<0, std::tuple<args_t...>>::type; template<template<size_t, typename> class func_t> using instantiate = std::tuple<typename func_t<0, this_type>::type>; template<typename object_t, typename... call_args_t> static void call(object_t&& obj, call_args_t&&... args) { this_type::call(std::forward<object_t>(obj), std::forward<call_args_t>(args)...); } template<typename object_t> static void call(object_t&& obj) { this_type::call(std::forward<object_t>(obj)); } }; /////////////////////////////////////////////////////////////// // for_each template<typename... args_t> struct for_each { using prev_type = for_each_impl<sizeof...(args_t) - 1, args_t...>; template<template<size_t, typename> class func_t> using instantiate = typename prev_type::template instantiate<func_t>; template<typename object_t, typename... call_args_t> static object_t call(object_t&& obj, call_args_t&&... args) { prev_type::call(std::forward<object_t>(obj), std::forward<call_args_t>(args)...); return obj; } template<typename object_t> static object_t call(object_t&& obj) { prev_type::call(std::forward<object_t>(obj)); return obj; } }; template<typename... args_t> struct for_each<std::tuple<args_t...>> : public for_each<args_t...> { }; template<> struct for_each<std::tuple<>> { template<template<size_t, typename> class func_t> using instantiate = std::tuple<>; template<typename object_t, typename... call_args_t> static object_t call(object_t&& obj, call_args_t&&... args) { return obj; } template<typename object_t> static object_t call(object_t&& obj) { return obj; } }; } //namespace helpers // ---------------------------- for_each.h end ----------------------------// using namespace helpers; ////////////////////////////////////////////////////////////////////////////////////////// template<int logUpTo> struct TLogTraitsStdErr { static void Open(const char* logName, int facility) { std::cerr << "std::err log opened with logName: " << logName << std::endl; } ~TLogTraitsStdErr() { std::cerr << "std::err log closed" << std::endl; } template<int logLevel, bool isNull> struct TSink { ~TSink() { std::cerr << "std::err log line flushed: \"" << stream.str() << " \"" << std::endl; } std::stringstream stream; template<typename T> TSink<logLevel, isNull>& operator << (T&& val) { stream << val; return *this; } }; template<int logLevel> struct TSink<logLevel, true> { template<typename T> TSink<logLevel, true>& operator << (T&& val) { return *this; } }; template<int logLevel, bool isNull> using sink_type = TSink<logLevel, isNull>; }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// template<int logUpTo> struct TLogTraitsSyslog { static void Open(const char* logName, int facility) { openlog(logName, LOG_PID | LOG_NDELAY, facility); setlogmask(LOG_UPTO(logUpTo)); } ~TLogTraitsSyslog() { closelog(); } template<int logLevel, bool isNull> struct TSink { ~TSink() { syslog(logLevel, stream.str().c_str(), "junk"); } std::stringstream stream; template<typename T> TSink<logLevel, isNull>& operator << (T&& val) { stream << val; return *this; } }; template<int logLevel> struct TSink<logLevel, true> { template<typename T> TSink<logLevel, true>& operator << (T&& val) { return *this; } }; template<int logLevel, bool isNull> using sink_type = TSink<logLevel, isNull>; }; ////////////////////////////////////////////////////////////////////////////////////////// template<char moduleName, typename object_t> struct TLogHeader { template<size_t idx, typename accessor_t> struct TLogHeaderWriter { using type = TLogHeaderWriter<idx, accessor_t>; static void call(typename accessor_t::object_t& obj, std::stringstream& out) { typename accessor_t::value_type& val = accessor_t::GetRef(obj); out << val << ":"; } }; template<typename sink_type> friend sink_type& operator << (sink_type& sink, object_t& val) { std::stringstream header; using writers = typename for_each<typename object_t::log_header_param_list_t>::template instantiate<TLogHeaderWriter>; for_each<writers>::call(*static_cast<object_t*>(&val), header); sink << moduleName << ":" << header.str(); return sink; } }; ////////////////////////////////////////////////////////////////////////////////////////// template<int logUpTo, template<int> class log_traits = TLogTraitsSyslog> struct TLog : public log_traits<logUpTo> { using trats_t = log_traits<logUpTo>; template<int logLevel, bool isNull> using sink_type = typename trats_t::template sink_type<logLevel, isNull>; template<int neededLogLevel, int logLevel> struct TGetLogOfLevel { using type = typename std::conditional< neededLogLevel == logLevel, sink_type<neededLogLevel, (neededLogLevel > logUpTo)>, typename TGetLogOfLevel<neededLogLevel, logLevel + 1>::type >::type; }; template<int neededLogLevel> struct TGetLogOfLevel<neededLogLevel, LOG_DEBUG + 1> { using type = void; }; template<int neededLogLevel> using Log = typename TGetLogOfLevel<neededLogLevel, 0>::type; }; /////////////////////////////// //testcase using TestLog = TLog<OUR_LOG_LEVEL, TLogTraitsStdErr>; struct CConnection : public TLogHeader<'c', CConnection> { CConnection(int _id, const std::string& _name) : m_id(_id), m_name(_name), m_state('a') { TestLog::Log<LOG_NOTICE>() << *this << "connection created"; m_state = 'b'; } ~CConnection() { TestLog::Log<LOG_NOTICE>() << *this << "connection destroyed"; } void DoSomething() { TestLog::Log<LOG_DEBUG>() << *this << "connection is doing something"; m_state = 'c'; } using this_t = CConnection; int m_id; using m_id_t = TParamAccessor<this_t, decltype(this_t::m_id), &this_t::m_id>; std::string m_name; using m_name_t = TParamAccessor<this_t, decltype(this_t::m_name), &this_t::m_name>; char m_state; using m_state_t = TParamAccessor<this_t, decltype(this_t::m_state), &this_t::m_state>; using log_header_param_list_t = std::tuple<m_id_t, m_name_t, m_state_t>; }; class CServer : public TLogHeader<'s', CServer>, public TestLog { public: CServer() : m_id(1), m_name("test server") { TestLog::Open("test_log", 1); }; int Run() { TestLog::Log<LOG_DEBUG>() << *this << "Creating connection"; CConnection test_conn1(23, "test_conn 1"); test_conn1.DoSomething(); return 0; } using this_t = CServer; int m_id; using m_id_t = TParamAccessor<this_t, decltype(this_t::m_id), &this_t::m_id>; std::string m_name; using m_name_t = TParamAccessor<this_t, decltype(this_t::m_name), &this_t::m_name>; using log_header_param_list_t = std::tuple<m_id_t, m_name_t> ; }; int main(int argc, char** argv) { CServer server; return server.Run(); } 

Here is a finished example.
In it, we have added another 100 lines of implementation of 2 options for_each.
And, if you looked at the example, you can see that they are in a separate file for_each.h
SinceI actively use it already in 5 modules of my history, such as: the native binary format of communication with postgres given the endianess of the end systems, the unfolding of packets from the buffers and the generation of the format of messages on the web socket at the compilation stage, etc.

And as already said: Stones! Welcome!

PS Code writing took half a day, and the article on Habr Day ... paradox!

Source: https://habr.com/ru/post/243571/


All Articles