ToString or not ToString?
Published 13 Jun 2026Most if not all primitive types in Bitcoin Core expose a ToString member
function. The pattern is so familiar that it rarely raises eyebrows. Yet it
deserves closer scrutiny, as it reflects a broader evolution in modern C++
library design. The real question is not how an object should produce a string
representation, but where that functionality belongs and what abstraction it
should expose.
Should ToString Be a Member Function?
The principle that functions should not be member functions unless necessary predates Bitcoin by a long time. Scott Meyers addressed the placement of member versus non-member functions as far back as the first edition of Effective C++ (1991). In his later article "How Non-Member Functions Improve Encapsulation", he took an even stronger stance: if a function can be implemented using a type's public interface, it generally should be a non-member.
All of the ToString implementations in Bitcoin Core rely solely on public
members. They should therefore be free functions. The improvement would look
like this:
std::string ToString(CTransaction const& tx);
Naming: Following the Standard's Lead
The second question is one of naming. The C++ standard library already provides
std::to_string, and many std facilities serve as extension points in other
namespaces.
Consider how range-based for loops rely on unqualified lookup for begin and
end, or how the idiomatic way to swap values is:
using std::swap;
swap(a, b);
This argument-dependent lookup (ADL) mechanism does not work if types define
Begin, End, or Swap instead. If the same logic applies here, calling a
free function ToString could break that extension mechanism. This is not a
question about naming style preference.
But was std::to_string ever truly meant to be an extension point? It may have
seemed so when introduced in C++11. However, C++26 settles the question
definitively by reimplementing std::to_string in terms of
std::format("{}", value). This makes it obvious that the answer is "No" and
reveals the actual standard extension point for converting to a string:
std::format.
The Modern Path Forward
The ToString member functions should ultimately be replaced with
std::formatter specializations. This aligns with the current direction of the
C++ standard and provides a clean, consistent way to integrate these types with
std::format, std::print, and other formatting facilities.
As an extra bonus, this approach reserves the possibility to add parameterization to the stringification in the future (for example, controlling precision, formatting style, or verbosity) without breaking existing interfaces or requiring additional overloads.