> I think the main purpose of a struct is not name scoping.
It is one of its characteristics.
When used strictly for namespacing purposes, I think the main difference is "namespaces are open for additions; structs are closed against additions."
FWIW, John Lakos' Large Scale C++, Volume 1 advocates structs over nested namespaces, and says the following:
(Figure 2-23, page 316)
There are many advantages of using a struct over a third level of namespace for aggregating related (what would otherwise be free) functions into a single utility component.
(1) The distinct syntax and atomic nature of a struct having static methods makes its purpose as a component-scoped entity clearer than would yet another, nested namespace, leaving namespaces for routine use at the package and enterprise levels exclusively.
(2) The self-declaring nature of functions and data defined at namespace scope are necessarily eliminated when they are instead nested (as static members) within a struct.
(3) Unlike a namespace, a struct does not permit using directives (or declarations) to import function names into the current (e.g. package) namespace, thereby preventing any consequent loss in readability.
(4) Unlike a namespace, a struct can support private nested data — e.g. as an optimization for accessing insulated (external bindage) table-based implementation details, residing in the .cpp file, by one or more inline functions, residing in the .h file.
(5) Unlike a namespace, a struct can be passed as a template parameter — e.g. as a cartridge of related functions satisfying a concept.
(6) Unlike a namespace, a C-style function in a struct does not participate in Argument-Dependent Lookup (ADL), thereby avoiding potentially large overload sets, which could needlessly affect compile-time performance and possibly introduce unanticipated (perhaps even latent) ambiguity, or — much worse — invoke the wrong function. By placing our "free" functions in a struct, we make our design decision not to employ ADL explicit.
(7) Except for a few very stylized cases, such as std::placeholders (e.g. _1, _2, _3) and std::literals, use of namespace declarations are generally ill-advised. Should we subsequently discover a rare valid engineering reason for enabling local using declarations, we can easily migrate a struct to a namespace by creating a new component-private struct, e.g. MathUtil_Imp, and forwarding calls to it from the new nested (e.g. MathUtil) namespace. Note that, except when used as in (5), it is always possible to migrate from a struct to a namespace without forcing any clients to rework their source code, but, given the possibility of using directives/declarations, not vice versa.
–Arthur