2

Let us consider the following function:

static void Print(const Type& type, const std::string& message, const std::string& variable) {
    Log(type, message + ": " + variable);
}

I'd like it to pass arbitrary number of variables (I mean std::string & variable - this holds a variable name) and then send them via Log() function together and for this reason, I've considered using template variadic function (an overloaded Print()). I'd define it like this:

template <typename Arg, typename ...Args)
static void Print(const Type& type, const std::string& message,
                  const Arg& arg, const Args&... args);

and then:

Print(type, message, args...);
Log(type, message + ": " + arg);

Just an idea, this would work most likely like this:

  • args... would be passed and Print() function would be called recursively until there's no arguments left,
  • but at the same time, Log() function would be called which would basically log it every time.

What I would need to do is to somehow remember arg value but it would require calling Print() with an additional argument and I don't really like this idea. Do you have any other clues?

2
  • 1
    You need a backing function that does the actual work. Any logging prefixes are appended to the log in Print, then everything else is handled recursively by, say, PrintImpl. Commented Aug 9, 2018 at 7:36
  • I've thought about this sort of solution here, it'll probably not be to much of a pretty thing but it would work, though. Commented Aug 9, 2018 at 7:39

3 Answers 3

3

It seems to me that the Max Langhof's solution is simple and elegant.

Unfortunately it uses template folding that is available only starting from C++17.

I propose a C++11/C++14 version that, instead template folding, uses the old trick of the initialization of an unused array

template <typename ... Args>
void Print (Type const & type, std::string const & message,
            Args const & ... arg)
 {
   using unused = int[];

   std::stringstream strstr;

   strstr << message << ": ";

   (void)unused { 0, (strstr << arg << ", ", 0)... };

    std::string toLog = strstr.str();

    // Remove last separator characters.
    toLog.erase(toLog.end() - 2, toLog.end());
    Log(type, strstr.str());
 }
Sign up to request clarification or add additional context in comments.

Comments

3

Depending on the desired format, you might be able to get away with a fold expression:

template<class... Args>
void Print(const Type& type, const std::string& message, const Args&... arg)
{
    std::stringstream strstr;
    strstr << message << ": "; // Or your prefix computation, whatever you want.

    ((strstr << arg << ", "), ...);

    std::string toLog = strstr.str();
    // Remove last separator characters.
    toLog.erase(toLog.end() - 2, toLog.end());
    Log(type, strstr.str());
}

Demo

2 Comments

I could have a condition there, which checks what the size of arg is and then just put dot at the end. Your solution seems reasonable as it doesn't require me to use a some sort of baking function that handles all the stuff. I'll try to play with it just a little bit.
You can still use the int[] trick to perform a pre-C++17 makeshift fold expression
1

I simplified your example a bit, so assuming I correctly understood what you want to do, you can do one of the 2 following solutions, if the C++17 folds suggested by @Max Langhof are not supported by your compiler.

Both of them work on any type that supports operator+ for doing the correct thing, but are simple to modify if your concat function is something else.

Option 1, recursive unpacking:

template <typename Arg>
static void Print(const Arg& message, const Arg& arg1)
{
    Log(message + ": " + arg1);
}

template <typename Arg, typename... Args>
static void Print(const Arg& message, const Arg& arg1, const Arg& arg2, const Args&... variables)
{
    Print(message, arg1 + ", " + arg2, variables...);
}

Option 2, unpacking into a std:vector:

template <typename Arg, typename... Args>
static void Print2(const Arg& message, const Arg& arg1, const Args&... variables)
{
    std::vector<Arg> args = { variables... };
    Arg result = std::accumulate(args.begin(), args.end(), arg1, [](const Arg& a, const Arg& b) {
        return a + ", " + b;});
    Log(message + ": " + result);
}

Be aware that this version will create copies of the arguments within the std::vector, unlike the other solution which will not.

Both examples can be used in the following fashion:

static void Log(const std::string& m)
{
    std::cout << m << std::endl;
}

int main()
{
    std::string msg = "MyMessage1";
    std::string var1 = "Var1";
    std::string var2 = "Var2";
    std::string var3 = "Var3";
    std::string var4 = "Var4";
    std::string var5 = "Var5";

    Print(msg, var1);
    Print(msg, var1, var2);
    Print(msg, var1, var2, var3);
    Print(msg, var1, var2, var3, var4);
    Print(msg, var1, var2, var3, var4, var5);
}

4 Comments

Thank you! @Max Langhof is great but, as it occurrs, I cannot use features of C++17. I've got a one question, do I actually need the non-template function if all the things are solved this way?
Do you mean the Log function? I wouldn't know, I just put it as a placeholder. Assuming you have some more elaborate logging mechanism (e.g sometimes logging to a file, sometimes over the network, sometimes just to std::cout), this is the place where you would invoke it. If all you want to do is call cout, by all means, skip the Log function and just cout in the termination version of the Print function (the one that takes only a single argument)
I meant Print function (without template) actually but you've probably gave me an answer to that as well (if you meant that the Print function that takes a single argument is the one without any template).
You don't need it - the version with signature static void Print(const Arg& message, const Arg& arg1) is the termination function, and you can do all of your processing there. Your 'arg1' will be the concatenated variables with a ', ' separator, and msg will be whatever you pass as msg

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.