Jonathan Boccara's blog

tee: Debug Info With Little Impact On Code

Published September 29, 2017 - 10 Comments

I’d like you to meet tee, a little companion for retrieving targeted runtime info, with very, very little impact on the code. It’s simple, but I find it very convenient.

You can put tee in your code wherever you need to know what’s going on, and tee will spy it for you. Its most basic implementation is this:

template <typename T>
T tee(T const& value)
{
    std::cout << value << "\n";
    return value;
}

There is a bit more to it to get all the details right, but more on this in a moment.

How to use tee

Maybe you guessed how to use tee by just looking at its implementation. You can fit it on any object or function call that you want to know the value of during the execution of your program, and it will output it for you on the standard output.

So:

tee(myValue);

is equivalent to:

std::cout << myValue << '\n';

You can also use it to retrieve intermediary values, with little impact on the code. In the following code:

myFirstFunction(mySecondFunction(myValue));

you can retrieve the value returned by mySecondFunction by plugging tee in:

myFirstFunction(tee(mySecondFunction(myValue)));

tee debug log info

You’ll note that this provides debug information without changing the look of the code. It’s a bit like a meter you fit on a pipe junction. But you can take it away when you don’t need it any more.

Getting the references right

The above implementation of tee gets the point across, but it doesn’t deal correctly with all cases of l-values and r-values. You may not care in your particular case but here is a more thorough implementation, that was the result of a discussion with Simon Brand and Björn Fahller about tee (thanks guys for your help!):

template <typename T>
decltype(auto) tee(T&& value)
{
    std::cout << value << "\n";
    return std::forward<T>(value);
}

So, the T&& when T is a template type is a forwarding reference (also called universal reference by Scott Meyers), that represents either an l-value reference or an r-value reference in accordance with what was actually passed to the function. You can read more about that in Item 24 of Effective Modern C++.

std::forward<T> maintains the l-value reference or r-value reference property of the object value. All about this in Item 23 of Effective Modern C++.

decltype(auto) comes in C++14 and retrieves the exact type of the returned value to determine the return type of the function, in particular by keeping the references. This avoids making a copy of the value that tee passes along. To get more details about this, head over to Item 3 of Effective Modern C++.

All this is also a way of saying that if all the techniques used in the above implementation aren’t crystal clear, then you should have a look at Effective Modern C++!

To be honest, I find these kind of cases sometimes hard to get right for all situations. If you see a case of references that isn’t correctly covered by tee, or if you see a way to improve the implementation, don’t hesitate to let me know.

A nice point that Gerald made in the comment section is that this C++14 component is easy to port to C++11 by replacing the decltype(auto) by a trailing return type:

template<typename T>
auto tee(T&& value) -> decltype(std::forward<T>(value))
{
   ...

Another output than std::cout

My former colleague Arnaud Bellec suggested that tee should work with other outputs than std::cout. And this sounds very reasonable to me.

However, I haven’t come across that need myself so I’ve never used a custom tee in a project. But I see two ways of approaching this.

The first one is to change the implementation of tee itself, or make another tee-like function if you need to use several outputs in the same piece of code.

The other approach is more sophisticated but lets you declare a new tee in one line of code. It consists in wrapping tee in a class that can generate as many tees as you’d like, by passing each time a function that prints the value:

template <typename Print>
class Tee
{
public:
    explicit Tee(Print print) : print_(print){}
    template <class T>
    decltype(auto) operator()(T&& value)
    {
        print_(value);
        return std::forward<T>(value);
    }
private:
    Print print_;
};

template <typename Print>
Tee<Print> make_tee(Print print)
{
    return Tee<Print>(print);
}

Now for a new tee:

auto myTee = make_tee([](auto const& value){ /* custom print... */ });

But again, I haven’t used this version in my code so I wouldn’t vouch for either one in particular for the most practical. How would you have gone about changing the output?

The origins of tee

tee is largely inspired from the Unix command of the same name. The Unix command takes a file in argument and is designed to be placed between other commands. It sends to stdout exactly what it receives in stdtin, but it also copies what comes in into the file it took in argument.

Here is an example using it:

grep "hello" * | tee output_of_grep.txt | wc -l

This command line searches all the lines containing “hello” in the files of the current directory (grep), copies the result of that search into the file output_of_grep.txt (tee), and then counts the number of lines in this result (wc).

Our C++ tee kind of does the same thing: you can plug it over any value or function call in your code, it writes that value into an output (std::cout for example) and it forwards that value so that the rest of the code can use it just like if it wasn’t there.

Fancy a cuppa?

Feel free to use tee in your code, and give me your feedback on it!

I’ve found it helpful to be able to plug it in and out of code quickly, in order to log information about an execution, as opposed to changing the structure of the code to fit in a logging feature. I hope you’ll find it useful too.

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin

Comments are closed