Search code examples
c++outputthread-safetymanipulators

a sleek C++ variadic named test output


I've made some variadic output macros, especially for test output purposes. Examples: C++-code // output: function file(first and last char) linenumber variable=value

L(i,b); // result e.g. {evenRCpower@ih2943: i=36 b=1  @}
L(hex,pd,dec,check.back(),even,"e.g."); 
  // result e.g.: {way@ih3012:  pd=babbbaba  check.back()=15216868001 even=1 e.g.  @}

They are working fine, having some minor restrictions, which can be lowered by more text analysis (restricted usage of comma and output specifications). Up to now, they are working at least under g++1z and c++1z. My questions:

  1. Main question: How to get the macro completely thread safe?
  2. Is there a solution needing no textual analysis, e.g. to avoid #VA_ARGS and get the names for every parameter separated?
  3. How to distinguish parameters from output manipulators (necessary)?
  4. What's to change for other (newer) C++ and g++ versions?
  5. Even to write one complete line into one local string using a locally defined outstream isn't completely solving the problem of output mixing in parallel work - but why? In which C++ version this problem will be solved? I'll give a simplified base version with use of "std::out" will work fine, but of course give bad results, when used in parallel threads:
#define WO_IS 1
#define L(...) locate(__FILE__,__LINE__,__func__,"\n{",": "," @}\n",#__VA_ARGS__,##__VA_ARGS__)

string argTExcludes = " hex dec std::hex std::dec ", funcExcludes = " setprecision ";

string argT(const string sarg,int &nextpos) { // NO exact analysis!! Simple strings and chars allowed, but parameters with commata like in f(x,y,"A,",','): crazy output !! 
  int i = nextpos+1, pos = i, follow = 0; string nom; bool apo = false; 
  for (; i < sarg.size(); i++) 
    if(sarg.at(i) == ' ') { ;
    } else if ((sarg.at(i) == ',')||(i == (sarg.size()-1))) { 
      nom = sarg.substr(pos,i-pos);
      if (argTExcludes.find(nom) != std::string::npos) { nextpos = i; return ""; };
      break;
    } else {
      if ((sarg.at(i) != ' ') && (!follow++) && ((sarg.at(i) == '"')||(sarg.at(i) == '\'')) ) apo = true; 
      if ((sarg.at(i) == '"') && ( (i==0)||((i > 0) && (sarg.at(i-1) != '\'')) )) { i++; while ((sarg.at(i) != '"') && (i < sarg.size())) i++; };
      if (sarg.at(i) == '(') { 
        nom = sarg.substr(pos,i-pos); if (funcExcludes.find(nom) != std::string::npos) apo = true; 
      };
    };
  nextpos = i;
  return (apo)?"":sarg.substr(pos,i-pos)+"=";
};

template <typename... Ts>
inline void locate(string ort,long line,string funct,const string prefix, const string trenner, const string postfix, string sarg, Ts... args) 
{ 
#if WO_IS > 0 // all range restrictions abandoned
    int pos = -1; bool apo; sarg += ",";
    std::ios_base::fmtflags f( cout.flags() );
    std::cout << prefix << funct << '@' << ort[0] << ort.back() << dec << line << trenner; 
    cout.flags( f );
    ((std::cout << argT(sarg,pos) << args << ' '), ...); // forbidden: endl - it will give syntax error even when no blank at end 
    // ((std::cout << argT(sarg,pos); std::cout << args; std::cout << ' '), ...); // forbidden: endl - it will also give syntax error
    if (postfix == "\0") std::cout.flush();
    else std::cout << postfix; //  << endl; 
#endif
};

Solution

  • Is there a solution needing no textual analysis, e.g. to avoid #VA_ARGS and get the names for every parameter separated?

    You might do it with hard coded limit.

    Some utilities MACRO to "iterate" over __VA_ARGS__:

    #define COUNT_VA_ARGS(...) TAKE_10(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
    #define TAKE_10(_1, _2, _3, _4, _5, _6, _7, _8, _9, N,...) N
    
    #define JOIN(a, b) JOIN_H(a, b)
    #define JOIN_H(a, b) a ## b
    
    #define WRAP_VA_ARGS_0(wrap)
    #define WRAP_VA_ARGS_1(wrap, x0) wrap(x0)
    #define WRAP_VA_ARGS_2(wrap, x0, x1) wrap(x0), wrap(x1)
    #define WRAP_VA_ARGS_3(wrap, x0, x1, x2) wrap(x0), wrap(x1), wrap(x2)
    #define WRAP_VA_ARGS_4(wrap, x0, x1, x2, x3) wrap(x0), wrap(x1), wrap(x2), wrap(x3)
    #define WRAP_VA_ARGS_5(wrap, x0, x1, x2, x3, x4) wrap(x0), wrap(x1), wrap(x2), wrap(x3), wrap(x4)
    // ...
    
    // Call into one of the concrete ones above
    #define WRAP_VA_ARGS(wrap, ...) JOIN(WRAP_VA_ARGS_, COUNT_VA_ARGS(__VA_ARGS__))(wrap, __VA_ARGS__)
    

    Then, you might do something like

    template <typename... Ts>
    void print(std::source_location loc, const Ts&...args)
    {
        std::cout << loc.function_name() << ":" << loc.line() << std::endl;
        ((std::cout << args << " "), ...) << std::endl;
    }
    
    #define NAMED_PAIR(x) #x, x
    #define PRINT(...) print(std::source_location::current(), WRAP_VA_ARGS(NAMED_PAIR, __VA_ARGS__))
    

    Demo