C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Revising #pragma once

From: Jeremy Rifkin <rifkin.jer_at_[hidden]>
Date: Tue, 27 Aug 2024 23:27:21 -0500
Hello,

I appreciate all the replies and feedback immensely. Because this
email thread has become sprawling and hard for me to follow, I'd like
to take a moment to summarize and address some points as well as
re-focus. I will incorporate all of these points into the next draft
of the proposal.


Is #pragma once a good thing?

While there are a couple notable edge cases (see 5.1) it is pretty
clearly a useful and valuable utility, as evidenced by its widespread
use. Additionally as discussed in section 2 it is both less clutter
and less error prone.

I have observed the same trend Thiago mentioned about codebases
trending towards adopting #pragma once as opposed to the other way
around. This is of course hard or impossible to quantify rigorously,
so take the anecdote as nothing more than an anecdote..


Why don't more people use it?

Pretty much anywhere you go to look for advice about #pragma once vs
traditional include guards you'll see people make the point about
portability and it being non-standard. From what I've seen it's not
more widely used because of the virtue of not being standard as well
as general FUD about implementation divergence. This is why I think it
should be standardized.

Anecdotally, I've seen countless discussions in C++ communities about
#pragma once that boil down to "#pragma once is great in practice but
it might be non-portable so just use #ifdef."

Ville shared:
> The industry refuses to use it because it doesn't work.
This is in part due to approaches which are flawed in varying ways.

Andrey shared:
> Any cross-platform project I've worked with did not rely exclusively on `#pragma once` - exactly because it is not portable


Should unqualified #pragma once be left as implementation-defined?

No. N2896 took this approach but I think the main point of
standardizing this is to alleviate concern about implementation
differences. In doing so this proposal will change the behavior of
existing implementations. Reasonable concern about this has been
expressed, however, from what I can see it's very possible to do this
in a way that both doesn't break existing code in practice and also
makes existing code more portable.


#once or #pragma once

Prior proposals both proposed #once. Folks on this email chain have
mentioned #pragma being by definition for implementation-defined
things - I also mention this in the paper. In the interest of
standardizing existing practice instead of adding something new I kept
the existing spelling. Additionally, keeping the spelling of #pragma
once allows existing code to benefit from standardization.
Additionally, we now have precedence for standard pragmas, see
6.10.7/2 of the C23 standard.


Regarding defining uniqueness

I am unsurprised this has been the most controversial part of this
proposal and many have shared they do not like this idea. There are
two choices if a behavior is to be standardized:
1. A filesystem-based definition (paths, inodes, stat, etc)
2. A content-based definition

I should clarify that I have not been entirely precise when pointing
to GCC as an example of using a content-based definition. Due to how
GCC uses mtime, i.e. assuming unique contents if the mtime hasn't been
seen before, it is participating in a filesystem-based definition with
fallback to a content-based definition.

I think it would be helpful to look at some case studies. Please do
not take my use of names here as an attempt to call people out by
name, but this is the easiest way to clearly refer to raised points.

Connor and Gašper mention the case of mount points. This is a footgun
with a filesystem-based definition but not a content-based definition,
nor traditional include guards.

The point about hard-links was briefly mentioned. This is a footgun
with a filesystem-based definition but not a content-based definition,
nor traditional include guards.

While discussing this, symbolic links are a footgun with some
filesystem-based definitions but not a content-based definition, nor
traditional include guards.

Gašper mentioned issues they have had with GCC due to mtime precision.
This is a footgun with a filesystem-based definition but not a
content-based definition, nor traditional include guards.

Sebastian brought up a case where a preprocessor_tools header with
only macros is included, then everything is #undef'd, then that header
is included again. While I think such a header should not be include
guarded and should instead only define things if they aren't already
defined, this is a case worth considering. This case is a footgun
under both filesystem-based and content-based definitions. It's a
footgun in all current implementations. Additionally it's a footgun
under traditional include guard unless the include guard identifier is
also #undef'd. I think this case might suggest a need for #pragma
forget id to pair with #pragma once id. It's not, however, a unique
footgun to either definition.

Jonas brought up a case with different "main headers" with the same
contents including other headers with relative paths. This is a
footgun for content-based definitions but not filesystem-based
definitions. I think it is reasonable to point out this can be
mitigated by not using an include guard for such a main header.

I brought up cases in 5.1 and 5.2 where a filesystem-based definition
results in footguns while neither a content-based definition nor
traditional include guards do.



In almost all cases, a content-based definition provides the expected
behavior while a filesystem-based definition leads to footguns.
Additionally it matches the behavior you'd get with traditional
include guards, which is what #pragma once is meant to replace. I very
much understand concern and wariness about this, however, as far as I
can tell a content-based definition provides the desired behavior in
practical code. We haven't yet seen a concrete practical example of a
content-based definition leading to surprises, other than Jonas'
point.

If it's deemed that it would be too much of a footgun for two files
with different names but the same contents to be treated as the same,
it might be reasonable and practical to add filename to the uniqueness
definition.

I'm trying to weigh what needs to be decided now and what should be
decided by the committee.


Lastly, since I was asked for a reference implementation:
diff --git a/libcpp/files.cc b/libcpp/files.cc
index fc66b9c3d73..947f1677eea 100644
--- a/libcpp/files.cc
+++ b/libcpp/files.cc
@@ -873,7 +873,7 @@ has_unique_contents (cpp_reader *pfile, _cpp_file
*file, bool import,

       if ((import || f->once_only)
          && f->err_no == 0
- && f->st.st_mtime == file->st.st_mtime
+ // && f->st.st_mtime == file->st.st_mtime
          && f->st.st_size == file->st.st_size)
        {
          _cpp_file *ref_file;

Cheers,
Jeremy


On Tue, Aug 27, 2024 at 9:01 AM Jeremy Rifkin <rifkin.jer_at_[hidden]> wrote:
>
> Hi,
> I have drafted a proposal for standardizing #pragma once. This has been previously proposed a few years ago and I recognize that on top of being difficult to standardize, existing opinions on this topic may render the paper dead on arrival. However, due to its widespread nature and concerns about portability contributing to it not being used more I think it's worth revisiting. I have uploaded the first draft at: https://jeremy-rifkin.github.io/cpp-proposals/drafts/pragma-once-draft-1.html.
>
> Cheers,
> Jeremy

Received on 2024-08-28 04:27:34