icecream-cpp replaces cout debugging with one macro call
🍦 Never use cout/printf to debug again
At a glance
- What is it?
- Icecream-cpp is a single header C++11 and later library for print debugging, where IC prints the file, line, function signature and the values of whatever you pass it. It is meant to live in your working copy during development and never in a shipped build, and the packaging choices are unusual enough to check before you commit to it.
- Who is it for?
- Adopt icecream-cpp when you work in C++11 or later, want to inspect values without hand writing a print statement, and can keep a debug dependency out of your release builds. Do not adopt it to replace a debugger, since the README positions it as a complement, and do not assume its licence terms from the MIT assumption common in this space, because the repository carries LICENSE.txt and the licence is classified as custom.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Activity is slowing. The repository last received commits 6 months ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A throwaway debugging header, not a logging library
Icecream-cpp is described in the README as a little single header library to help with print debugging in C++11 and later, and it says plainly that it was inspired by the original Python IceCream library. The rationale section sets the intended usage more carefully than most libraries do. It calls the project a throwaway library, designed for use during development and debugging rather than for shipping with the released product, and says it complements debuggers rather than replacing them, since sometimes print debugging is simply faster or more practical.
That framing explains the odd ratio the README mentions, that in a library like this you will spend more time writing code with it than reading code with it. The stated design goals are expressivity and conciseness: print any variable, format it as desired, with as little code as possible.
The audience, then, is a C++ developer with a build in progress who wants to see a value in the terminal in the next minute, and who will delete the calls before shipping. It is not a structured logging framework with levels, sinks or rotation.
IC with no arguments prints the file, line and signature
The simplest call prints the prefix, the source file name, the current line number and the current function signature:
auto my_function(int foo, double bar) -> void
{
// ...
IC();
// ...
}The README shows the resulting line as `ic| test.cpp:34 in "void my_function(int, double)"`. The same call with arguments prints the argument names and their values instead, so the following:
auto v0 = std::vector<int>{1, 2, 3};
auto s0 = std::string{"bla"};
IC(v0, s0, 3.14);produces a line reading `ic| v0: [1, 2, 3], s0: "bla", 3.14: 3.14`.
Two mechanics are visible in that output. The `ic|` prefix makes the line easy to spot and easy to filter out of a noisy log, and the argument names come from the source text rather than from the values, which is why a literal such as `3.14` is printed with its own name attached. The original motivation example in the README is the same idea applied to control flow: a chain of `std::cout` lines marking entry into a function and which branch was taken becomes three `IC()` calls, and the printed output shows the file and line of each one, so the trace is read from the log instead of remembered.
IC_F takes a format string, including range slicing
The main entry points are `IC`, `IC_A` and `IC_V`, each with a formatted counterpart that accepts an output formatting string as its first argument: `IC_F`, `IC_FA` and `IC_FV`. Formatting replaces the usual stream state fiddling. Where a manual print would set `std::hex` and rebuild the string, the README shows:
IC_F("#x", answer, num);which prints `ic| answer: 0x2a, num: 0xa`.
The format syntax goes further than printf style. The README demonstrates slicing a container with a range format string, the same syntax as a Python slice, so:
auto v = std::vector<char32_t>{U'α', U'β', U'γ', U'δ', U'ε', U'ζ'};
IC_F("[1:-2]", v);prints `ic| v: [1:-2]->['β', 'γ', 'δ']`. That is a debugging convenience rather than a formatting language in its own right, but it means the common question of what is inside the middle of a vector can be asked without writing a loop.
The `IC_A` and `IC_FA` variants are the return value and apply macro forms listed in the README's contents, and the table of contents also lists sections on configuration, printing strategies, third-party libraries and compatibility, so the formatting and output layers have more surface area than the three macros suggest.
IC_V prints lazily inside a range views pipeline
Insert `IC_V()` into a pipeline of STL ranges or Range-v3 views and the data flowing through is printed as it is produced. The README's example splits a vector on zeroes and enumerates the pieces:
auto rv = std::vector<int>{1, 0, 2, 3, 0, 4, 5}
| vws::split(0)
| IC_V()
| vws::enumerate;Iterating the result yields lines such as `ic| range_view_63:16[0]: [1]`, `ic| range_view_63:16[1]: [2, 3]` and `ic| range_view_63:16[2]: [4, 5]`.
The laziness is the interesting part, and the README is explicit about it. Nothing is printed when the range is created; printing happens while iterating, one line per element generated. A second example with `vws::iota('a') | vws::enumerate | IC_V() | vws::take(3)` prints exactly three lines, one per loop iteration, which means the inspection does not change what the pipeline computes.
The catch is that the printed identifier, such as `range_view_63:16`, is a generated name for the view, not a name you chose. Reading the output of a deep pipeline therefore takes some interpretation, and the reference section of the README lists a C strings topic and a character encoding topic, which suggests that how such generated text is printed is a documented concern rather than an afterthought.
Copying the header is the install; CMake, Conan and Nix are wrappers
The library is a single file with only the STL as a dependency, and the README says the simplest way to use it is to copy `icecream.hpp` into your project. That is the whole installation story for many people, and it is also the reason the header lives at the top level of the repository next to `CMakeLists.txt` and `icecream-cpp-config.cmake`.
For a system-wide install with CMake support, the README gives these commands to run from the project root:
mkdir build
cd build
cmake ..
cmake --install .Once installed, a CMake consumer finds it with:
find_package(icecream-cpp)
target_link_libraries(<target> PRIVATE icecream-cpp::icecream-cpp)where the target is the executable or library being compiled. The header itself is then included as:
#include <icecream.hpp>For package managers, the README points at Conan, where released versions are available, with a usage example in `example_project/conanfile.txt`, and at Nix, where the project is consumed as a flakes input and can also be referenced directly or through an overlay that adds an `icecream-cpp` derivation to the nixpkgs attribute set. A working flake lives in `example_project/flake.nix`. Three different routes to the same header, which tells you the packaging was an afterthought rather than the point.
The overhead is that the diff stays noisy
The cost of the approach shows up in version control. Every `IC` call is a line you will delete later, and the library's own framing, a throwaway tool for development, admits that. If you adopt it, you want the calls out before release, and the README's table of contents includes a reference topic called macro disabling, alongside C strings and character encoding, which indicates that turning the macros off is a supported step rather than a hack.
There is a second cost, and it is about correctness of what you read. The printed values record one point in the program, taken at the line where you wrote the call. If you want to know what a value becomes after some other code runs, the call has to be moved. This is the ordinary property of print debugging, and the reason the library positions itself next to a debugger instead of replacing one.
For a codebase that is large and unfamiliar, the value is speed of orientation rather than depth of analysis. For a codebase with a hot loop, an `IC` inside the loop will print on every iteration, and the library gives you no documented way to silence it in production other than removing or disabling the macro.
Package contents, releases and the licence question
The repository is small and flat: `icecream.hpp`, `CMakeLists.txt`, `icecream-cpp-config.cmake`, `flake.nix`, `flake.lock`, `LICENSE.txt`, `AUTHORS`, a `tests/` directory, an `example_project/` directory and a `ci_images/` directory. Tests exist, which is not always the case with single header libraries.
Release history is thin in a way worth noting. The tags are v1.0.0 from 2025-03-05, then a long gap back to v0.3.1 and v0.3.0, both from 2020-04-04. A one point zero release after five years of 0.3.x tags suggests the API was considered settled rather than that the project was churning, but it also means there are no incremental releases to cherry pick from. The repository is not archived and the last push was on 2026-04-22, which is after the 1.0.0 tag.
On the licence, the metadata for the repository classifies it as a custom licence that GitHub cannot categorise, and the terms live in `LICENSE.txt` in the repository root. Read that file before shipping the header in a commercial product, and note that the MIT terms often assumed for small C++ utilities are not the ones stated here.
Editorial conclusion
Adopt icecream-cpp when you work in C++11 or later, want to inspect values without hand writing a print statement, and can keep a debug dependency out of your release builds. Do not adopt it to replace a debugger, since the README positions it as a complement, and do not assume its licence terms from the MIT assumption common in this space, because the repository carries LICENSE.txt and the licence is classified as custom. Verify first that your compiler supports the `IC` macro and the `IC_V` range pipeline together, that the header you copy is the same one your CMake target links against via `find_package(icecream-cpp)`, and that the version you take, v1.0.0 from 2025-03-05, matches the source you copied, given that the last push was on 2026-04-22.
Frequently asked questions
What does IC() print in icecream-cpp?
Called with no arguments, IC prints the prefix, the source file name, the current line number and the current function signature, for example `ic| test.cpp:34 in "void my_function(int, double)"`. Called with arguments it prints those argument names and their values instead.
How do I install icecream-cpp?
The simplest route is to copy `icecream.hpp` into your project, since the library is single file and header-only with only the STL as a dependency. The README also documents a CMake install with `mkdir build`, `cd build`, `cmake ..` and `cmake --install .`, plus Conan and Nix consumption.
What does IC_V() do in a ranges pipeline?
IC_V() prints the data flowing through an STL ranges or Range-v3 pipeline as it is generated, so nothing is printed until you iterate the result and one line appears per iteration. IC_FV is the same thing with a format string as its first argument.
Is icecream-cpp suitable for production code?
The README calls it a throwaway library designed for use during development and debugging, not for shipping with the released product, and says it complements debuggers rather than replacing them. Its own table of contents includes a reference section on macro disabling for turning the calls off.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/renatogarcia-icecream-cpp)Was this useful?