The printf and scanf families cross a variadic boundary: the format string determines how argument values are interpreted. C++ does not make a mismatched call safe. A bounded wrapper must validate both the format contract and the storage contract.
Match every argument exactly
Use %d for int, %ld for long, %lld for long long and %zu for size_t. The * width and precision arguments are int. Default promotions matter: float becomes double in printf arguments, while scanf %f expects float* and %lf expects double*. A pointer to a destination is not interchangeable with a promoted value.
Keep the format trusted and pass external text as data. A name containing %n must stay literal text, not become a write directive. Our metric formatter uses %.*s with an explicit view length, because string_view.data() need not have a NUL at the view boundary. The precision counts bytes, not Unicode code points, and does not make overlapping snprintf source/destination buffers valid.
Follow one complete example
Run this program, then change one boundary while keeping the API preconditions valid. Predict its output before pressing Run.
#include <cstdio>
int main() {
char word[8]{}; int consumed=-1;
int assigned=std::sscanf("orchard rest","%7s%n",word,&consumed);
std::printf("%d %s %d\n",assigned,word,consumed);
char short_output[5]{};
int needed=std::snprintf(short_output,sizeof short_output,"%s","orchard");
std::printf("%d %s\n",needed,short_output);
}Interpret snprintf as both a count and an operation
For nonzero capacity, snprintf writes a terminated prefix when output is truncated. Its nonnegative return is the count that would have been written with enough room, excluding NUL. A result equal to capacity therefore means truncation. Check the signed return for an error before converting to size_t.
A zero-capacity call writes nothing and can use a null destination pointer, but still needs valid arguments for its conversions. An empty string_view may have a null data pointer, so pass a valid empty string for %s rather than relying on an implementation printing a special null spelling. The exercise keeps the truncated prefix and reports truncation explicitly; another API could instead format privately and commit only complete output.
Bounded scanning still needs a parser policy
For %s and a scanset, a scanf field width limits payload characters but excludes the appended NUL: %7s needs at least eight destination bytes. %c does not add a terminator. Check assignment counts; a successful conversion can leave trailing bytes. The %n conversion can record consumed input but does not increment the assignment count, and must never come from an untrusted format.
Numeric scanf conversions are not a robust validation interface for arbitrary out-of-range numbers: the portable contract does not safely represent every overflowing result. Read bounded input and use the checked strtol/strtod sequence for hostile numeric fields. gets was removed from the C standard and provides no acceptable bounded-input policy. Compare new C++ output code with std::format/std::print while retaining these rules for real C interfaces.
Read the return value
Predict this smaller case. The check item later changes the values, so explain the rule as well as the output.
#include <cstdio>
int main(){char b[5];int n=std::snprintf(b,sizeof b,"%s:%d","abc",12);std::printf("%d %s\n",n,b);}Complete the faded example
State the missing precondition or result check before filling the code. The fill exercise supplies the rest of the implementation.
int required=std::snprintf(dst, capacity, "%.*s=%lld", precision, name, value);
// Fill: handle negative required before converting it.
// Fill: distinguish exact-capacity truncation from complete output.Check your model
A four-byte payload formatted into capacity four returns four. Why does the wrapper report truncation even though the number sounds like an exact fit?
Practice the boundary
Implement format_metric(std::span<char> output, std::string_view name, long long value) -> std::expected<std::size_t,format_error> with snprintf.
Write name=value in decimal using a trusted format. Name is data, so percent signs in it are literal. Name may be an unterminated string_view; it must not overlap output. Reject embedded NUL or a name length larger than INT_MAX as invalid before any output mutation. Empty names are valid. The caller retains the "C" locale.
On success return the character count excluding NUL. Report a negative snprintf result as encoding. Report truncation when its nonnegative would-have-written count is at least the output capacity, including zero capacity. For nonempty output, preserve snprintf's truncated, terminated prefix; do not overwrite beyond the supplied span. Invalid input leaves output unchanged. Zero capacity writes nothing. The terminator requires a separate byte. The variadic precision has type int and the value has type long long.
Read → six checks → two bug hunts → fill → build. A successful graded submission is evidence; reading or watching alone does not grant mastery. Review cards reinforce API choices separately.
Watch and verify
The Watch & read panel links a free lecture for the relevant background and primary API references. The lecture notes say which parts are background; the tested contract here supplies the exact wrapper policy.
C standard-library course · C library review cards · Unix/Linux systems