Page MenuHomeFreeBSD

style.9: Add a C++ section
ClosedPublic

Authored by jhb on Jun 23 2025, 5:18 PM.
Tags
None
Referenced Files
Unknown Object (File)
Thu, Aug 7, 10:23 PM
Unknown Object (File)
Tue, Jul 29, 9:09 AM
Unknown Object (File)
Tue, Jul 29, 8:03 AM
Unknown Object (File)
Tue, Jul 29, 8:02 AM
Unknown Object (File)
Tue, Jul 29, 7:33 AM
Unknown Object (File)
Tue, Jul 29, 4:54 AM
Unknown Object (File)
Tue, Jul 29, 4:16 AM
Unknown Object (File)
Tue, Jul 29, 2:39 AM
Subscribers

Details

Summary

This section adds several style guidelines for C++ in FreeBSD's base
system both enumerating some differences relative to the C KNF style
and addressing some unique C++ idioms not addressed by the existing
KNF style.

Sponsored by: Chelsio Communications

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Skipped
Unit
Tests Skipped
Build Status
Buildable 65132
Build 62015: arc lint + arc unit

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
share/man/man9/style.9
989

IMO, yes. i find a missing newline after a template parameter list very hard to read.

also, we should require typename rather than class.

992
1007

i don't have a very strong opinion here but i would vote for this style:

auth(const char *secret, const char *mutual_user,
     const char *mutual_secret)
: a_secret(secret)
, a_mutual_user(mutual_user)
, a_mutual_secret(mutual_secret)
{}

this style is fairly common and easy to read.

1047
1068

we should forbid use of either new or malloc except in cases where there's absolutely no alternative (which are extremely rare).

1082

the LLVM/libc++ version in FreeBSD 13-STABLE and later provides both <format> and <print>, so we could already require use of std::print if we wanted to.

i would be in favour of this, but it does mean code has to be compiled with -std=c++23 which might be an issue for libraries intended to be consumed by third-party code.

In D50983#1164248, @jhb wrote:

TODO: Should probably forbid using namespace std?

some people allow this at function scope. i would support forbidding it anywhere, since it's pointless, std:: is not hard to type.

however, we should still allow aliases of std namespaces at function scope:

namespace stdr = std::ranges;
namespace stdv = std::views;

and we should also allow

using namespace std::literals;

at function scope, since otherwise the literals are quite hard to use, and standard library literals cannot conflict with user-defined literals anyway.

In D50983#1164252, @jhb wrote:

TODO: Should we prefer using to typedef?

typedef should be forbidden (except in C compatibility headers), there's never any reason to use it in C++. using using everywhere makes code more consistent and easier to read, and follows the left-to-right reading style of modern C++ syntax.

In D50983#1163664, @jhb wrote:

It's not clear to me that AAA has been universally adopted

it hasn't and i don't think we should require it, but i do think we should allow it, including for function return type. this is still an emerging aspect of C++ style and i think forbidding it would be unnecessarily restrictive.

however we should specify how trailing return types are to be formatted.

for short functions:

auto f() -> int;

for longer functions?

auto very_long_function_name(very_long_concept<templated> auto &&argument_type)
        -> int;
share/man/man9/style.9
1068

also: we should forbid use of C strings (char *) except as necessary to interface with C code. std::string is preferable for both memory- and type-safety, and has better interactions with modern C++ features like ranges. for lightweight strings (where we don't want to allocate memory), std::string_view should be used.

share/man/man9/style.9
921

I suppose it is a style for C, not just primarily for C. Superficial aspects of KNF also apply to shell scripts etc., but I think "as a style for C code." is valid

934
989

Yeah, I think this is preferable over

template <class T> class box<T> {

so probably just s/may be/are/.

I wouldn't want rfc2119-style "MUST be followed" but "should be followed by a newline" could work as well.

1041

My previous C++ $DAYJOB used m_ prefix. I've seen _ prefix discouraged elsewhere.

LLVM's proposal from 2019ish https://llvm.org/docs/Proposals/VariableNames.html has:

A possibility is for member variables to be prefixed with m_ and for global variables to be prefixed with g_ to distinguish them from local variables. This is consistent with [LLDB]. The m_ prefix is consistent with [WebKit].

A variation is for member variables to be prefixed with m [IvanovicDistinguish] [BeylsDistinguish]. This is consistent with [Mozilla].

Another option is for member variables to be suffixed with _ which is consistent with [Google] and similar to [Python]. Opposed by [ParzyszekDistinguish].

The argument against _ suffix https://lists.llvm.org/pipermail/llvm-dev/2019-February/129941.html is:

IMO, any convention that contains leading or trailing underscores should
be rejected outright. The primary purpose of a convention is to allow a
person to differentiate between different kinds of elements with a quick
glance. It should strive to make these elements appear different without
sacrificing the readability. Prepending or appending a lone underscore
is really making the identifier as similar to another one as possible,
while still making it different from the language standard point of
view, i.e. the opposite to what a useful convention should do.

I agree with this perspective.

1082

Maybe "is discouraged" rather than "ban"? I think it's fine to be upfront that in the future we will require C++23 and use <format> even though that will be at least a few years from now.

share/man/man9/style.9
934

and pedantically, the "STL" hasn't been a thing since C++98, it's just the standard library now.

I like "the C++ Standard Library" rather than just "the standard library"

936

I agree with dropping "generally." "Should" by itself gives already enough leeway if an exception is needed.

1082

the LLVM/libc++ version in FreeBSD 13-STABLE and later provides both <format> and <print>, so we could already require use of std::print if we wanted to.

In that case, perhaps iostream is discouraged, <format> and <print> may be used, with a caveat about libraries that might be used outside of the base system.

share/man/man9/style.9
1082

per IRC, no base source may use <format> or <print> at all, so it doesn't make sense to ban iostream; how else are you going to print anything?

we should revisit this when the base C++ standard is bumped.

share/man/man9/style.9
934

i'm fine with either "standard library" or "C++ Standard Library" or anything along those lines, but "standard template library" is wrong.

1041

i didn't originally have an opinion here but on reflection i also agree that m_foo is probably the best option: it's clear and unambiguous, and won't conflict with anything.

olce added inline comments.
share/man/man9/style.9
940

Don't know this term either, and not sure what it means. It can as well stay, but in any case a definition is warranted.

1007

Having punctuation at the start of the line feels quite ugly, is not easier to read than having it at end and is surprising with respect to all other rules/common practice we already have.

It sometimes makes sense to group initialization of some members, so I'd just suggest some style but generally leave it open to the committer as to where newlines should be inserted. All listed styles below seem acceptable to me. Having a different indent for the first initializers may be a good idea (as is done in example 3 below; that could be applied to example 2 as an alternative) but perhaps complicates editors' configuration?

1041

Suggest p_ as prefix? Generally, I'm not sure if we should be that prescriptive here.

share/man/man9/style.9
942

This is not always the practice I've seen (everything in a namespace). It's true in some places, not in others. And in particular, that might be a question worth raising (should we require namespaces everywhere?) I do think I have seen namespaces always used in lld for example (though always accompanied with using namespace foo in headers so that they are mostly invisible except for the cases where they are (like explicit template instantiation, or when template substitution fails in a non-obvious fashion). OTOH, GDB (which was originally a C program) only uses the gdb:: namespace in some headers, but not globally for all of the symbols. I suspect that most use of C++ in FreeBSD in the short term will consist of migrating existing utilities vs writing new ones so will be closer to GDB.

I'm fine though if we think namespaces should not be indented. I suspect most editors will default to indenting which will be a bit of a pain to deal with.

956

Yes. You can still do grep ^<function name> (albeit you have to use the qualified name).

989

Is there a specific reason for preferring typename to class in a template parameter list? cppreference.com seems to claim there is no difference between the two:

https://en.cppreference.com/w/cpp/language/template_parameters.html

1007

The function parameters probably need to follow style(9) just as in C which uses 4 space continuations. I think the list of initializers should likely also follow that rule. But given those starting assumptions, I think the question is if
we explicitly separate the the parameter list from the initializers and how.

1041

Maybe we just don't say anything here. Existing practice for KNF C members is that structures "often" (but not always) have a prefix for member names that is based on the enclosing type, e.g. p_<foo> for members of struct proc. In ctld I've just kept the existing prefixes and not done anything special. Perhaps that existing paradigm is sufficient.

1068

Yes, I agree with using std::string in new code, and often even when converting existing code it ends up being far simpler.

1068

Re: new, see the next note below about make_* being preferred.

An interesting case is what to do when you need a "buffer of bytes". GDB has a specialization of a vector<> of bytes that avoids zero-initialization. We may need to add explicit guidance about the "buffer of bytes/char" case though (probably either an array<> or vector<>?)

1082

For some context, in main we currently use C++17 as our baseline as we wish to keep FreeBSD buildable with external toolchains available on supported versions of macOS and various Linux LTS distributions which may not have compilers as new as version of LLVM in base. In terms of how else you will print anything, I suspect most near-term use of C++ in FreeBSD will consist of converting existing tools which are already using printf() and will likely just continue to use printf(). <format> is a more natural conversion path away from printf() than adding an intervening conversion to iostream for a few years.

I also find iostream horrifically unreadable as a syntax, but that alone is probably not a reason to ban it outright.

share/man/man9/style.9
949–951

I'm not sure what the intent is here with inline function declarations? I assume it's just compactness? That would be inconsistent with regular prototypes using a TAB (and possibly an extra space).

Instead, I would suggest just relaxing the rules for all prototypes/declarations instead (which are already not always obeyed, IIRC).

share/man/man9/style.9
942

i'm not sure it makes sense to mandate using a namespace or not (especially for converted code) so i'm fine with just removing the indent requirement for namespaces.

956

i might be misreading but i don't think this is true. "Nested inline function definition inside of a ... namespace should not use a newline":

namespace foo {

void do_something()
{
...
}

} // namespace foo

this will not be matched by `grep '^do_something'.

989

there is no difference[0], which is why we should specify one or the other :-)

IME, typename is preferred in nearly all modern C++ code because the template parameter can be any type, not just a class.

[0] i think there is, or used to be, a difference in a very uncommon edge-case (possibly involving template-template parameters) which i don't recall the details of off hand, but practically, there is no difference.

1041

fwiw, i dislike Unix-style struct prefixing in C++. in C, this is done to ensure you're accessing the correct struct; IIRC, it dates from before structs even had types, so the prefix was the only way to do "type" checking, but it's also useful in code that does a lot of casting between struct types. (it's also useful when doing something like #define p_foo p_someunion.foo.)

in C++, you almost never access a struct member from outside the struct itself (because you should be using an accessor) which makes this practice obsolete.

1068

i don't think this is a common enough situation to warrant discussion in style(9)?

if you want a fixed-size uninitialised buffer, you are going to use std::array<> anyway because that's the type that does that. however, even in that case i would always initialise the buffer to zero, because it avoids accidentally leaking stack data in case of other bugs. (i'm not suggesting we should mandate that, though; while i personally think it's better, there are performance considerations that mean it's not appropriate in every case.)

1082

I also find iostream horrifically unreadable as a syntax, but that alone is probably not a reason to ban it outright.

i concur (this is why <print> was invented!) but as i mentioned on IRC, iostream is also the only way to print C++ objects.

the alternative is every object would need to define a to_string() function so you can call printf("%s", to_string(obj).c_str()) and this is terrible: firstly you lose the type safety you get for free with iostream, and secondly to_string() is probably just going to call operator<< on its members anyway.

basically, iostream sucks, but it's what we have in C++.

jhb marked 7 inline comments as done.Jun 25 2025, 1:51 PM
jhb added inline comments.
share/man/man9/style.9
934

As others have noted, STL is really a dated term and "template" is no longer relevant in the name. I'm fine with including an explicit "C++" though I think from the context of being the C++ section it is already implied.

936

The exception case is given in the next sentence (converting existing C code). I'm fine with dropping "generally".

940

Cuddle braces is:

if (foo) {
}

vs

if (foo)
{
}

Similarly for class and struct definitions, etc. KNF generally uses cuddle braces except for function bodies which do not. I guess I can say that the opening brace should be on the same line. (I have seen the cuddle brace phrase used in various contexts when discussing style of C-like languages over the years.)

jhb marked 12 inline comments as done.

Various review feedback

share/man/man9/style.9
949

Hmm, declarations is probably more correct, but style(9) uses prototypes in the rest of the manual.

949–951

Also more typical of C++ in general, and the fact that member functions are already indented unlike C function prototypes. The space separator also leaves room for compact function definitions.

950

This one is a callback to "function prototypes" used elsewhere in the manual page for C functions, so probably should stay as-is. (Maybe a separate commit should do a giant s/prototypes/declarations/ pass.)

956

Hmm, I had envisioned in this case that you would still use:

void
do_something

That is, for function definitions that aren't inlined as part of a struct/class/union member, they still follow the C rule? I think the issue here is that I was previously assuming that namespaces were indented. I can remove namespace from here instead.

1007

Emacs by default wants to line up additional lines of initializers with the first initializer, so akin to example 4, though by default it does not indent the colon that way by default. Here are some styles Emacs uses out of the box with the current knf settings:

	auth(std::string_view secret, std::string_view mutual_user,
	    std::string_view mutual_secret) :
		a_secret(secret), a_mutual_user(mutual_user),
		a_mutual_secret(mutual_secret) {}
	auth(std::string_view secret, std::string_view mutual_user,
	    std::string_view mutual_secret)
		: a_secret(secret), a_mutual_user(mutual_user),
		  a_mutual_secret(mutual_secret) {}

I think of those, I might prefer the first. You end up with a full tab for continuation lines, but it does provide some visually clear separation between function parameters and initializers.

Add a note about types for strings

share/man/man9/style.9
956

i'm fine with the current wording (without "namespace") but indented namespaces are a good point.

i don't think it makes sense to write this:

namespace N {
        void
        func()
        {
         ...
        }
}

because this just looks weird and still doesn't make ^func work.

we could say that indented namespaces shouldn't have the newline, but i'm leaning towards resolving this by saying that namespace contents should not be indented.

1004
1057

if this is for declaration of operator():

struct T {
  void operator()();
};

... then for consistency i think not. operator() () is not any more readable to me than operator()().

also, soon you will be able to write void operator()(this T &self) which makes it not look so weird.

1068
1085

it is not immediately clear to me what "return values of make_unique" means here. how would you use auto with make_unique?

1091
1105
share/man/man9/style.9
1085

never mind, i see what you mean. but i think this is more clear.

one other thing that occurs to me: we should not require variable definitions to be at the start of the function in C++, since they often can't be.

jhb marked 3 inline comments as done.Jun 30 2025, 6:49 PM
jhb added inline comments.
share/man/man9/style.9
1091

You still need delete with smart pointers, it's just new that is replaced?

share/man/man9/style.9
1091

You still need delete with smart pointers

not unless you're doing something unusual? what i was trying to express here is that unique_ptr can usually replace both new and delete:

old code:

int f() {
  auto *o = new T;
  int ret = o->calculate_something();
  delete o;
  return ret;
}

new code:

int f() {
  auto o = std::make_unique<T>();
  return o->calculate_something();
}

both new and delete are replaced by the unique_ptr here, and at least in my experience, this is true of nearly all uses of smart pointers.

jhb marked 2 inline comments as done.Jun 30 2025, 7:02 PM
jhb added inline comments.
share/man/man9/style.9
1057

Yes, I'm probably leaning towards not. I had used them previously, e.g.:

	/*
	 * nvlist_up is a std::unique_ptr<> for nvlist_t objects which
	 * uses nvlist_destroy() to destroy the wrapped pointer.
	 */
	struct nvlist_deleter {
		void operator() (nvlist_t *nvl) const
		{
			nvlist_destroy(nvl);
		}
	};

Specializations of std::hash<> are often similar.

1068

Hmm, for C we already forbid 0, but for C++ 0 is more acceptable historically so that is a decent point.

jhb marked 2 inline comments as done.

Another round of fixes

share/man/man9/style.9
1091

Oh, it's true that I rarely have to explicitly use delete as usually the smart pointers are members of some other containing object (whether a node in a container or some other class/struct) so often the delete is implicit (or buried in some template such as for containers). And I often end up using reset() to clear a smart pointer "early". That is orthogonal to std::make_*? That is, the case I was worried about is more like:

std::unique_ptr<Foo> foo(new Foo(....));

vs

auto foo = std::make_unique<Foo>(...);

I think the point you are making is that smart pointers should be used instead of plain pointers to manage object life times. It is true that I find I do need to use plain pointers obtained via get() to deal with aliases passed around that do not affect object lifetimes, but for the pointer that "owns" an object, those should be smart pointers and not plain pointers.

Explicitly state that smart pointers should be used

other than the ctor init list formatting (which i still have no real opinion on) i'm happy with what we have now. thank you for dealing with so much feedback :-)

but i thought of another thing we might want to consider, which is formatting of requires clauses. i would suggest always putting these on a new line with no indentation, i.e.:

template<typename T>
requires(std::integral<T>)
void 
f(T &&o)
{
        return o;
}

and

void
f(auto &&i)
requires(std::integral<decltype(i)>)
{
        return i;
}

i mention this because a lot of people like to indent these (i think clang-format might even do that by default) but it looks odd to have one line indented in a 4-line function declaration.

share/man/man9/style.9
1091

ah, we were talking about slightly different things, but the new wording covers both cases :-)

This revision is now accepted and ready to land.Jul 1 2025, 8:05 PM

One minor thing that wasn't mentioned here (and is likely be brought up in future C++ reviews) is the order of C++ includes. More specifically, should the C++ standard library includes come before or after /usr/include files?

I also assume we would prefer to include "C++-style" C headers over the standard .h C includes (i.e., <cstdio> over <stdio.h>).

One minor thing that wasn't mentioned here (and is likely be brought up in future C++ reviews) is the order of C++ includes. More specifically, should the C++ standard library includes come before or after /usr/include files?

I also assume we would prefer to include "C++-style" C headers over the standard .h C includes (i.e., <cstdio> over <stdio.h>).

So far I have been grouping C++ headers "after" C headers in my work to date, but that's mostly been focused on converting existing code. In terms of <cstdio> vs <stdio.h>, I think this depends on if you are writing new code from scratch vs converting existing C code. For the latter case, it's probably simplest to stick with C headers to avoid sprinkling std:: in many places. I also think that in many ways we want to encourage the use of C++ APIs when possible in new code (e.g. use std::string methods and helpers instead of strtok, etc.) in which case you really shouldn't be using many of the "c<mumble>" headers?

Drop the multiline constructor question

This revision now requires review to proceed.Wed, Jul 23, 6:23 PM
This revision was not accepted when it landed; it landed in state Needs Review.Mon, Jul 28, 6:32 PM
This revision was automatically updated to reflect the committed changes.