C++ Logo

sg15

Advanced search

Re: [SG15] [isocpp-modules] Determining identity like #pragma once

From: Gabriel Dos Reis <gdr_at_[hidden]>
Date: Wed, 10 Jul 2019 23:03:29 +0000


On Jul 10, 2019, at 3:40 PM, Richard Smith <richardsmith_at_[hidden]<mailto:richardsmith_at_[hidden]>> wrote:

On Wed, Jul 10, 2019 at 3:15 PM Michael Spencer <bigcheesegs_at_[hidden]<mailto:bigcheesegs_at_[hidden]>> wrote:
On Wed, Jul 10, 2019 at 2:51 PM Gabriel Dos Reis via SG15 <sg15_at_[hidden]<mailto:sg15_at_[hidden]>> wrote:


| -----Original Message-----
| From: Bryce Adelstein Lelbach aka wash <brycelelbach_at_[hidden]<mailto:brycelelbach_at_[hidden]>>
| Sent: Wednesday, July 10, 2019 2:37 PM
| To: Gabriel Dos Reis <gdr_at_[hidden]<mailto:gdr_at_[hidden]>>
| Cc: Tom Honermann <tom_at_[hidden]<mailto:tom_at_[hidden]>>; modules_at_[hidden]<mailto:modules_at_[hidden]>;
| sg15_at_[hidden]<mailto:sg15_at_[hidden]>
| Subject: Re: [isocpp-modules] [SG15] Determining identity like #pragma once
|
| On Wed, Jul 10, 2019 at 11:02 AM Gabriel Dos Reis <gdr_at_[hidden]<mailto:gdr_at_[hidden]>>
| wrote:
| >
| > You are implying that the include translation is mandatory. Is that your
| argument?
|
| It is my belief that include translation is mandatory.
|
| "If the header identified by the header-name denotes an importable
| header ([module.import]), the preprocessing directive is instead
| replaced by the preprocessing-tokens: import header-name ;"
| https://nam06.safelinks.protection.outlook.com/?url=http%3A%2F%2Feel.is<http://eel.is>
| %2Fc%2B%2Bdraft%2Fcpp.include%237&amp;data=02%7C01%7Cgdr%40mic
| rosoft.com<https://nam06.safelinks.protection.outlook.com/?url=http%3A%2F%2Frosoft.com&data=02%7C01%7Cgdr%40microsoft.com%7C65c60a7cb411418a573c08d705879d5b%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C636983952331426276&sdata=CVxYgPI7ql6HcosoLYyLX3v%2B3JVvy1LBW4uHO2RMNEE%3D&reserved=0>%7C57adb75b608743ebf6e208d7057ed5d4%7C72f988bf86f141af
| 91ab2d7cd011db47%7C1%7C0%7C636983914618863287&amp;sdata=JsvGf
| 0RfvcjOSMF6q%2FfsW60hhKx5TI%2F1K42M8PdRbOU%3D&amp;reserved=0
|
| To me, that reads as "including an importable header must be
| translated to an import".
|
| Was that the original design intent?

The original intent, as I understood it, was to *permit* include translation.

A mandatory include translation is practically unworkable for the MSVC audience. I wouldn't be surprised at all if that is the case of other compilers too.

A permission, as opposed to a requirement, would allow adoption of header import as better and principled replacement of PCH. Otherwise, it would create more problems than it solves.

-- Gaby


I was there for most of the LEWG discussion on D1502R0 (Standard library header units for C++20), and I can not say with confidence that the room understood the implication that this would require include translation. There don't seem to be any minutes of the discussion on the wiki.

However, the paper does explicitly point this out:

> This has a number of consequences, such as:
> * ...
> * Existing #includes of standard library headers transparently turn into module imports in C++20

It also in the wording explicitly adds the headers to the set of implementation-defined importable headers.

> An importable header is a member of an implementation-defined set of headers that includes all importable C++ library headers (16.5.1.2 [headers])

So I believe the author's intent was to require that include translation occurs.

+ Richard so we can ask them :)

Yes, my intent was to (effectively) require include translation for these parts of the standard library.

Thanks for clarifying the intent.

In practice, an implementation could satisfy that requirement by instead providing standard library wrapper headers that simply contain a relevant import declaration -- this does not strictly require an implementation to actually do include translation during preprocessing.

That is indeed one possible conforming workaround. Another is what MSVC has been doing, but I would like to proceed differently with a more standards-blessed technique.


An implementation that doesn't actually do include translation at all is still permissible using this technique. Such an implementation would need to document that its implementation-defined set of importable headers is empty other than the standard library headers. If they did so, then any "import header-name;" construct that didn't name a standard library header would violate [module.import]/5's "H shall identify an importable header." Such an implementation could still choose to accept such an import as a language extension. I would not encourage such an approach, but I think it's consistent with the wording we have.

The problem with mandatory include translation is that it precludes existing conforming implementation techniques of headers where certain parts are driven from the outside by macros. Those techniques are necessary for incremental adoption.


If we want to permit "import header-name;" without requiring include translation, and we want to allow re-exporting of imported header units, we'll need to fix the robustness issues that arise when a legacy header's macros (particularly include guards) are divorced from its semantic contents. (Nathan pointed this out during prior EWG discussion, and this non-robustness is the reason we require include translation for importable headers.) For example:

// foo.h
#fndef FOO_H
#define FOO_H
struct X {};
#endif

// TU A
export module M;
export import "foo.h";

// TU B
import M;
// suppose we make a language change that allows this #include to be textually included rather than subject to include translation
// then because the FOO_H macro is not visible here (because modules can't export macros), we get...
#include "foo.h" // error, redefinition of X

The same issue arises with the global module fragment:

// TU C
module;
#include "foo.h"
export module M;
export X f(); // definition of X is reachable through this TU

// TU D
import M;
#include "foo.h" // error, redefinition of X

I think there's a reasonable fix to both of these; instead of rejecting a redefinition when a prior definition is reachable, we should only reject a redefinition of an entity attached to the global module when there's a prior definition in the same translation unit. The ODR would apply as normal to such global-module-owned redefinitions. (I think I've suggested this on either the evolution or modules reflector before.)

That roughly matches what MSVC has been doing for the last 3 years (in addition to fabricating a hash based on the content of the header for “include guard” purposes.)

I am supportive of exploring this path of bugfix for C++20.


If we make that change, both the global module fragment and header units would become more robust, and we would no longer need to require include translation for all importable headers. (I think we should still require include translation for the standard library, though as noted above, an implementation can model that by providing header files that contain imports.)

And that would help with adoption of header imports as better replacement of PCHs.

— Gaby

Received on 2019-07-10 18:05:26