On Fri, Sep 06, 2024 at 00:03:23 -0500, Jeremy Rifkin wrote:
> Hello,
> 
> I'm looking at #pragma once behavior among the major C/C++ compilers as
> part of a proposal paper for standardizing #pragma once. (This is
> apparently a very controversial topic)
> 
> To put my question up-front: Would GCC ever be open to altering its
> #pragma once behavior to bring it more in-line with behavior from other
> compilers and possibly more in-line with what users expect?
> 
> To elaborate more:
> 
> Design decisions for #pragma once essentially boil down to a file-based
> definitions vs a content-based definition of "same file".
> 
> A file-based definition is easier to reason about and more in-line with
> what users expect, however, distinct copies of headers can't be handled
> and multiple mount points are problematic.
> 
> A content-based definition works for distinct copies, multiple mount
> points, and is completely sufficient 99% of the time, however, it could
> potentially break in hard-to-debug ways in a few notable cases (more
> information later).
> 
> Currently the three major C/C++ compilers treat #pragma once very differently:
> - GCC uses file mtime + file contents
> - Clang uses inodes
> - MSVC uses file path
> 
> None of the major compilers have documented their #pragma once semantics.
> 
> In practice all three of these approaches work pretty well most of the
> time (which is why people feel comfortable using #pragma once). However,
> they can each break in their own ways.
> 
> As mentioned earlier, clang and MSVC's file-based definitions of "same
> file" break for multiple mount points and multiple copies of the same
> header. MSVC's approach breaks for symbolic links and hard links.
> 
> GCC's hybrid approach can break in surprising ways. I have three
> examples to share:
> 
> Example 1:
> 
> Consider a scenario such as:
> 
> usr/
>   include/
>     library_a/
>       library_main.hpp
>       foo.hpp
>     library_b/
>       library_main.hpp
>       foo.hpp
> src/
>   main.cpp
> 
> main.cpp:
> #include "library_a/library_main.hpp"
> #include "library_b/library_main.hpp"
> 
> And both library_main.hpp's have:
> #pragma once
> #include "foo.hpp"

Could a "uses the relative search path" fact be used to mix into the
file's identity? This way the `once` key would see "this content looked
for things in directory `library_a`" and would see that
`library_b/library_main.hpp`, despite the same content (and mtime) is
actually a different context and actually perform the inclusions?

Of course, this fails if `#include "../common/foo.hpp"` is used in each
location as that *would* then want to elide the second inclusion. I
don't know how this problem is avoided without actually reading the
contents again. But the "I read this file" can remember what relative
paths were searched (since the contents are the same at least).

> Example 2:
> 
> namespace v1 {
>     #include "library_v1.hpp"
> }
> namespace v2 {
>     #include "library_v2.hpp"
> }
> 
> Where both library headers include their own copy of a shared header
> using #pragma once.

Again, the context of the inclusion matters, so "is wrapped in a scope"
can modify the "onceness" (`extern "C"` is probably the more common
instance).

> Example 3:
> 
> usr/
>   include/
>     library/
>       library.hpp
>       vendored-dependency.hpp
> src/
>   main.cpp
>   vendored-dependency.hpp
> 
> main.cpp:
> #include "vendored-dependency.hpp"
> #include <library/library.hpp>
> 
> library.hpp:
> #pragma once
> #include "vendored-dependency.hpp"

This is basically the same as Example 1 as far as context goes.

Note that context cannot include `#define` state because `#once` is
defined to be the first thing in the file and a file that is intended to
be included multiple times (e.g., Boost.PP shenanigans) in different
states cannot, in good faith, use `#once`.

Hrm…though if we are doing `otherdir/samecontent`, the different
preprocessor state *might* change that "what relative files did we look
for?" state… Nothing is easy :( .

--Ben

Reply via email to