Should We Use std::printf in C++?

The very first C++ standard, C++98, introduced headers such as <cstddef>, <cstdio>, and <cstdlib> to replace the corresponding standard C headers like <stddef.h>, <stdio.h>, and <stdlib.h>. The difference between the new headers and the standard C headers is that all names in these new headers, except macros (which have no namespaces), are considered to be declared or defined in the namespace std. Furthermore, names defined in the C headers are considered equivalent to being defined in the namespace std and then placed into the global namespace with using-declarations:

Each C header, whose name has the form name.h, behaves as if each name placed in the Standard library namespace by the corresponding cname header is also placed within the namespace scope of the namespace std and is followed by an explicit using-declaration (7.3.3).

(C++98 depr.c.headers D.5/2)

Do you notice the name of this section is ‘depr.c.headers’? C headers are considered deprecated—generally speaking, this means that one day, C++ code that includes <stdio.h> might not compile!

Perhaps for this reason, some coding standards explicitly prohibit the use of C headers. The MISRA C++ 2008 standard is one such example:

Rule 18-0-1 (Required) The C library shall not be used.

Rationale

Some C++ libraries (e.g. <cstdio>) also have corresponding C versions (e.g. <stdio.h>). This rule requires that the C++ version is used.

In other words, under such coding standards, you should include <cstdio>, and naturally, you should use std::printf instead of printf.

Ignoring whether there are benefits to write in this way for now (we’ll come back to this point later), I have seen many code pieces, under similar coding standards, in which people included headers like <cstdio>, but continued to use names like printf. . . .

Wait, according to the C++ standard, shouldn’t this fail to compile? In headers like <cstdio>, shouldn’t printf be declared in the namespace std ?

The C++98 standard expected the following (too optimistically): the contents of the standard library, except for macros, should all be defined or declared in the namespace std, including those coming from C. So, it believed <cstdio> would declare printf in the namespace std. So, it believed <stdio.h> should also put things into std first, then inject them into the global namespace.

Let’s see what actually happened. All major compiler vendors believed <stdio.h> was a C language header, only shared with C++. Therefore, they never implemented it the way C++98 expected. In fact, their approach is just the opposite: <cstdio> includes <stdio.h>, and then uses using to inject the names that were expected to be defined/declared in the namespace std into the namespace std. Like the following:

// cstdio

#include <stdio.h>

namespace std
{
  using ::FILE;
  using ::fpos_t;

  using ::clearerr;
  using ::fclose;
  using ::printf;
  …
}

Therefore, C++ code that includes the <cstdio> header can still use printf under such implementations, although it does not comply with the standard.

By the way, this implementation approach has been completely legal since C++11. For example, the same D.5/2 section of the C++11 standard was as follows:

Every C header, each of which has a name of the form name.h, behaves as if each name placed in the standard library namespace by the corresponding cname header is placed within the global namespace scope. It is unspecified whether these names are first declared or defined within namespace scope (3.3.6) of the namespace std and are then injected into the global namespace scope by explicit using-declarations (7.3.3).

In this case, does it make sense to use <cstdio>? At least, it’s completely impossible not to pollute the global namespace. Does std::printf look better and more concise than printf?

From a practical perspective, I see no benefits at all.

Only disadvantages.

When proficient C programmers migrate to C++, do they need to modify their original C code? This is especially awkward for Unix (including Linux) programmers: if you wanted to write code in the way the C++ standard wanted, you would need to write std::fopen(…), but not std::open(…)fopen and open are both part of the POSIX specification (i.e. the Unix standard), but only fopen is part of C++.

Should a Unix programmer care whether a POSIX function is included in the C++ standard?—I would consider it a ridiculous requirement.

If you are in doubt, keep in mind that you will never observe std (or its mangled form) for C functions in the object files. std::printf, in essence, is just the C printf.

If one included <cstdio> but continued to use printf, it would be even worse—the code would be incompliant and in the danger of not compiling one
day.


But the C headers have been deprecated. . . . Wouldn’t it be dangerous to keep using them?

In fact, when reviewing the deprecated facilities of the C++ standard, the C++ Standards Committee has at least twice (P0619 and P2139) proposed to change the deprecated status of the C headers. We can clearly see people’s opinion on this issue from the documents:

Finally, it seems clear that the C headers will be retained essentially forever, as a vital compatibility layer with C and POSIX. After two decades of deprecation, not a single compiler provides a deprecation warning on including these headers, and it is difficult to imagine circumstances where they would, as any use of a third-party C library by a C++ project is almost guaranteed to include one of these headers, at least indirectly. Therefore, it seems appropriate to undeprecate the headers, and find a home for them as a compatibility layer in the main standard.

After P2139, there is another proposal specifically to clarify the status of the ‘C headers’. P2340 further writes:

Ever since C++ has been an ISO standard, the C headers have been “deprecated”, which in ISO parlance means “no longer in use”, “discouraged”, or “subject to future removal”. However, this is inappropriate: The C headers are a necessary part of the C++ ecosystem. . . .

In this author’s opinion . . . which seems to resonate with many other committee members, that original decision (which goes back to the first standard, C++98) was never quite perfect in the first place, and many have since come to regard the deprecated status of the headers as a mistake.

I am pleased to see that the C headers are finally undeprecated in C++23. From the draft standard N4950, we can see that the C headers are no longer part of ‘Annex D Compatibility features’, but appear in ‘17 Language support library’. We no longer need to worry about the legitimacy of using <stdio.h> (but the C++ standard still discourage the use of C headers, about which I have a different opinion).


Does this mean we should always prefer <name.h> to <cname>? No, I won’t go that far.

My personal opinion is that this is a style issue, and the style should depend on the context. It should depend on which way is more natural, and which way makes the code more readable. Does your C++ code purely use C++ facilities, or does it use ‘C facilities’? In my view, printf is a C facility, while size_t is much less C-specific (code that doesn’t call any C functions at all still needs to use std::size_t quite often). So, I would use <stdio.h> and printf, but I would also, very often, use <cstddef> and std::size_t.

Of course, there are always contexts in which neither way seems obviously better. For example, is uint8_t considered C or C++? I am inclined to think that if the code uses a lot of C functions, then it might as well use <stdint.h> and uint8_t. This is simpler and more concise, if nothing else.

Another point to note is that there are things defined in headers like <cname> but completely absent in C. The typical cases I can think of immediately are:

  • std::byte is defined in the <cstddef> header. To use std::byte, you must include <cstddef>.
  • The C abs function only accepts int arguments, while the C++ std::abs has overloads that can accept arguments of other types like long or double. If you want to use those overloads, you absolutely need to use std::abs and <cstdlib>/<cmath>, instead of abs and <stdlib.h>.

In fact, I’ve been using the <name.h> in C++ for many years, but in recent years I’ve also been using <cstddef> happily when writing pure C++ code. This article is partially a justification of my intuition and style.

In any case, coding rules that unconditionally require avoiding <stdio.h> should be abandoned: modifications incurred by such rules do not bring practical value in most cases, and are much likely to introduce problems that make the code fail to comply with the C++ standard.