Draft of a module design

1,731 views
Skip to first unread message

Gabriel Dos Reis

unread,
May 11, 2014, 6:58:33 PM5/11/14
to mod...@isocpp.org, c++st...@accu.org
Hi,

Attached to this message is a PDF copy of an early *draft* of a module design for C++. We intend to submit a proposal for Rapperswil and would appreciate early feedback.

The draft does not contain changes in Standardese yet because we are focusing on the design first.

Comments, suggestions appreciated.

-- Gaby

spec.pdf

Loïc Joly

unread,
May 11, 2014, 7:35:26 PM5/11/14
to c++st...@accu.org, mod...@isocpp.org
Le 12/05/2014 00:58, Gabriel Dos Reis a écrit :
> Hi,
>
> Attached to this message is a PDF copy of an early *draft* of a module design for C++. We intend to submit a proposal for Rapperswil and would appreciate early feedback.
>
> The draft does not contain changes in Standardese yet because we are focusing on the design first.
>


Just a first remark after a fast surface reading of the document:
If export clauses are closed, as 4.2.5 seem to imply, maybe we can get
rid of the export keywork:
module M2;
export { bool g(int, int); }

could be rewritten:
module M2
{
bool g(int, int);
}

---
Loďc

Gabriel Dos Reis

unread,
May 11, 2014, 7:44:49 PM5/11/14
to mod...@isocpp.org, c++st...@accu.org
Not everything that is declared/defined in a module is necessarily available to the outside. Consequently we do need to say (in some form) what the interface consists of. Section 4.2.4 explains why we need a clear designator for that. See also the first two paragraphs of 4.2.


-- Gaby

Loïc Joly

unread,
May 11, 2014, 7:57:57 PM5/11/14
to mod...@isocpp.org
Le 12/05/2014 01:44, Gabriel Dos Reis a écrit :
> |
> | Just a first remark after a fast surface reading of the document:
> | If export clauses are closed, as 4.2.5 seem to imply, maybe we can get
> | rid of the export keywork:
> | module M2;
> | export { bool g(int, int); }
> |
> | could be rewritten:
> | module M2
> | {
> | bool g(int, int);
> | }
>
> Not everything that is declared/defined in a module is necessarily available to the outside. Consequently we do need to say (in some form) what the interface consists of. Section 4.2.4 explains why we need a clear designator for that. See also the first two paragraphs of 4.2.
>
>

I agree for the need to declare what will be exported. I just suggest an
alternative syntax (although I believe we are probably too early for
syntax...)
Sorry for not beeing clear in my sample:
// File 1
module M1; // M1 is a module that does not export anything
{
bool g(int, int); // Not exported
}

// File 2
module M2
{
bool g(int, int); // Exported
}
bool g2(int, int); // Not exported


Václav Zeman

unread,
May 12, 2014, 8:17:18 AM5/12/14
to mod...@isocpp.org, c++st...@accu.org
Using mandatory separate `export` section seems to be adding
duplication. You would have to provide function signature in the
`export` block and then for function definition. Would it not be
possible to overload the `public` keyword instead to mark exported
entities?

I can imagine that the `export` block could be used to, e.g., export
only one specialization of a template, so just using `public` keyword
might not be enough and both `export` block and `public` keyword might
be useful.

--
VZ

Gabriel Dos Reis

unread,
May 12, 2014, 9:05:43 AM5/12/14
to c++st...@accu.org, mod...@isocpp.org
| -----Original Message-----
| From: Maxim Kartashev [mailto:maxim.k...@oracle.com]
| Sent: Monday, May 12, 2014 4:39 AM
| To: c++st...@accu.org
| Cc: mod...@isocpp.org
| Subject: [c++std-ext-14833] Re: Draft of a module design
|
| Can you expand on why import declarations are transitive (the ones that
| are inside export section, that is)?

Import declarations outside of the interface section are not transitive.
On the other hand, import declarations in the interface section are transitive so that users aren't required to know the list of every single module needed to be included before using the module that makes uses of entities from those imported modules in its interface. This is a widespread practice today in the C++ world, and there is no compelling reason to take that away.

| It seems counter-intuitive as other
| languages with notion of modules (Modula-2, Java, Python) do not
| "export" declarations imported into module. And it seems like a good
| thing as it doesn't promote habit of having one huge all-purpose module
| that imports everything.
|
| Transitive imports also bring the problem with have with #inlcludes into
| modern era: when a.h includes b.h and users of a.h _and_ b.h start
| depending on that and do not think of b.h as an immediate dependency;
| this causes portability issues with STL implementations, for example.

If module M uses type T from M2 in its interface, users of M do have a dependency on M2. There is no reason to hide that.

It is not at all obvious what portability issues with STL implementations you are referring top. Are you envisioning a world where standard library implementers would be granted the right to import any other module in interface sections? That is the real problem. I see no a priori reason to grant that.

| Of
| course, with modules this kind of problem could be avoided by placing
| import outside of export section, but since it is so easy to write an
| import declaration, I think this would be a common scenario of import
| misuse, maybe on a smaller scale than with #includes, but still.

Gabriel Dos Reis

unread,
May 12, 2014, 9:37:54 AM5/12/14
to c++st...@accu.org, mod...@isocpp.org
| -----Original Message-----
| From: Ville Voutilainen [mailto:ville.vo...@gmail.com]
| Sent: Monday, May 12, 2014 5:21 AM
| To: c++st...@accu.org
| Cc: mod...@isocpp.org
| Subject: [c++std-ext-14835] Re: Draft of a module design
| Question: how is this related to the earlier module proposals/papers?
| Is it "competing"
| with them, a replacement, a synthesis? What's the implementation status, is
| this
| in cooperation with the people trying to do implementation work for modules,
| or
| a completely separate effort?

Obviously, C++ can't have 3 module systems :-)
We do know of two module efforts that we reference in the draft, as acknowledged:

# The proposal is informed by the current state of the art regarding module systems in contemporary programming languages, past suggestions [3, 5] and experiments such as Clang’s [1, 4], ...

As you may have noticed, we have consistently asked for more information about the various module efforts here on this reflector, or elsewhere including at face-to-face standards meetings. Modules are important to us and our customers, for reasons this SG was created, and also explained in the draft. We would like to see progress. We consider `modules' fundamental to the success of C++ in our ecosystem and would like to see an active community effort to solve this problem.

Not two C++ compilers are designed equal, so we need to agree on design points and we need a specification, not just a documentation of an implementation. This draft is meant to start the design and specification discussion alive and to keep it going. The draft proposal is based on our internal analysis of what we need to see improved with respect to the current header file inclusion model. Yes, we are also working on an implementation in our compiler -- as we would anyway if the C++ community embraced modules (which we believe it should). This isn't a competition, and it wouldn't be useful to frame it that way.

-- Gaby

Klaim - Joël Lamotte

unread,
May 12, 2014, 10:53:21 AM5/12/14
to mod...@isocpp.org

On Mon, May 12, 2014 at 1:57 AM, Loïc Joly <loic.act...@numericable.fr> wrote:
// File 1
module M1; // M1 is a module that does not export anything
{
  bool g(int, int); // Not exported
}

// File 2
module M2
{
  bool g(int, int); // Exported
}
bool g2(int, int); // Not exported

I think there might be issues with "ordering" code in cases like this:

    Module M1; // have to be the first statement
    
    class K;
    
    export
    {
        void foo( K& ); // is "class K;" implicitely exported?
    }
    
    class K { ... };

    export 
    {
         // some other stuffs
    }


But without practical use we can't know if these kind of cases would happen in real code.






Maxim Kartashev

unread,
May 13, 2014, 6:55:16 AM5/13/14
to c++st...@accu.org, mod...@isocpp.org


On 05/12/14 05:05 PM, Gabriel Dos Reis wrote:
> | -----Original Message-----
> | From: Maxim Kartashev [mailto:maxim.k...@oracle.com]
> | Sent: Monday, May 12, 2014 4:39 AM
> | To: c++st...@accu.org
> | Cc: mod...@isocpp.org
> | Subject: [c++std-ext-14833] Re: Draft of a module design
> |
> | Can you expand on why import declarations are transitive (the ones that
> | are inside export section, that is)?
>
> Import declarations outside of the interface section are not transitive.
OK, I understand that users are going to have a choice.
> On the other hand, import declarations in the interface section are transitive so that users aren't required to know the list of every single module needed to be included before using the module that makes uses of entities from those imported modules in its interface.
I seem to begin to understand the reason for this requirement. Does it
come from the rule that exported declarations can't reference
non-exported entities? It's not very clear from the proposal. If that's
the case, then imports must be transitive, of course.

I still encourage you to consider selective import (along the lines of
what Mikhail Semenov suggested) in order to avoid polluting interface of
importing module with unwanted declarations that effectively become part
of its interface.

With transitive imports, author of importing module seem to be
responsible for its interface, but may not have any control over
interfaces of modules it imports. If the latter changes in
non-backward-compatible way, importing module automatically becomes
backward-incompatible even if it doesn't use anything that changed.


> And it seems like a good
> | thing as it doesn't promote habit of having one huge all-purpose module
> | that imports everything.
> |
> | Transitive imports also bring the problem with have with #inlcludes into
> | modern era: when a.h includes b.h and users of a.h _and_ b.h start
> | depending on that and do not think of b.h as an immediate dependency;
> | this causes portability issues with STL implementations, for example.
>
> If module M uses type T from M2 in its interface, users of M do have a dependency on M2.
Right, but I'm talking about some T2 from M2 that is not used in M, but
is still available to users of M (unintentionally, of course). When
users switch to different implementation of M (that is better structured
and does not import everything from M2) and they didn't explicitly
import M2 because there was no need, they might see that T2 has
disappeared. I've seen this kind of problems with different STL
implementations.

Richard Smith

unread,
May 18, 2014, 5:18:44 PM5/18/14
to mod...@isocpp.org, c++st...@accu.org
This seems like a great basis for further work.

There's one area in particular that I'd like to see discussed more: how does an existing project incrementally transition to modules, such that they can still compile both with compilers that support modules and with compilers that do not? Clang's modules implementation allows this, by:
 1) Allowing #includes to be optionally and transparently upgraded to module imports (importing the portion of a module's interface that is described within the included file),
 2) Allowing modules to choose to export macros as part of their public interface, and
 3) Separating all of the module configuration from the source files that would be fed to a non-modules-aware compiler.
Clang's approach is almost certainly not the best approach to this problem, but it does turn out to be extremely practical; we have built projects of significant sizes with very few source changes (and none that break non-modules builds). Perhaps the answer is that we don't want to support code that can optionally use modules-based features if they're available (to encourage the world to transition), and that the transitionary problems should be left to implementors, but I find that unsatisfying.

I would like more concrete discussion of how the proposal might integrate with a build system. In particular, if module units nominate the module that they are included within, how is a build system to determine how to build a particular module? Must that information be duplicated elsewhere? Must the module dependencies be listed both within the build system and within the source files themselves? Will we have tools that scan the module units looking for dependencies? How do I write the equivalent of today's -MD-based builds (where the build system is told nothing of the dependencies, and computes it by building the code)?

Another area that I'd like to see covered is cyclic dependencies. It is probably reasonable to have a module X and a module Y, where the implementation of X uses Y and vice versa (but the *interfaces* are unordered) -- and early experiments with modules shows that this situation arises extremely frequently. For this reason, I think it might be wise to distinguish those module units of a module that are necessary to determine its interface description from those that are not; there can be no dependency cycles between modules considering only module units that are part of the interface of the module. The same need not be true when considering all the module units within a module.

Gabriel Dos Reis

unread,
May 18, 2014, 11:27:13 PM5/18/14
to c++st...@accu.org, mod...@isocpp.org

Fully agreed!

 

Any volunteer? :-)

 

-- Gaby

 

From: Matt Austern [mailto:aus...@google.com]
Sent: Sunday, May 18, 2014 6:30 PM
To: c++st...@accu.org
Subject: [c++std-ext-14848] Re: [modules] Draft of a module design

 

This suggests that a one-pager requirements document, i.e. listing the problems that a module design ought to solve, would be useful. It would be especially useful if it turned out that different people have different ideas of what problems matter.

Lawrence Crowl

unread,
May 19, 2014, 11:47:42 PM5/19/14
to mod...@isocpp.org, c++st...@accu.org
One part of the proposal concerns me, the ability to export an import.

Consider a module M1 that imports a modules M2, M3 and M4.
M2 contains symbols used in some of M1's exported symbols.
M3 contains symbols used in some other of M1's exported symbols.
M4 contains symbols used in the implementation of M1.

The ability to export an import introduces additional unnecessary
symbols into the namespace in two ways.

First, we can expect programmers to export { import M4; }.
We know that this is a bad idea. However, the programmer of M1 will
be providing a 'service' to the user of M1 because of perceived of
providing an "almost certain to want anyway" module.

Second, we can expect programmers to export { import M2; import M3; }.
A user of M1 may not use all the symbols of M1. In fact, they may
use only the symbols in M1 that use symbols only from M2.

In both cases, those exported imports will almost certainly create a
wider and deeper module import hierarchy, which has two problems.

First, importing M1 will be slower because it must navigate a larger
information space. There is a potential to get clever here, but we
are talking about modules because the large hierarchy of include
files has become a problem. Let's not replicate it.

Second, users of M1 may gather unappreciated dependences on symbols.
This problem happens already with standard headers that sometimes
do and do not include others.

Modules should provide substantial information hiding. Exporting
imports weakens that hiding. A better approach is to require
every translation unit to import the modules for the symbols it is
actually using.

--
Lawrence Crowl

Lawrence Crowl

unread,
May 20, 2014, 8:25:44 PM5/20/14
to c++st...@accu.org, mod...@isocpp.org
On 5/20/14, Mikhail Semenov <mikhail...@hotmail.com> wrote:
> I think the point is we can allow three options:
> (1)
> module X1;
> import X2; // import what is necessary into the export section;
> // this is the main feature;
> // items that are not used in the export section
> // are not exported
> export { ...}
> ...
>
> (2)
> module X1;
> export { import X2; ...} // import everything
> // (should be used with caution)
> ...
>
> (3)
> module X1;
> export {...}
> import X2; // use only in the implementation; don't export
> ...

Actually, I prefer a fourth option:

(4)
module X1;
import X2; // import for use in defining the exported symbols
export { set_of_symbols... } // export symbols
export { import X3; } // illegal, reexport not permitted

So, given a type T in M1, the code

module M2;
import M1;
export { void f(T); }

would export f, but WOULD NOT export T. That is, a client of M2
must explicitly import M2 in order for f to be usable.

This approach prevents silent injection of new symbols into the
clients namespace as the implementation of M2 evolves over time.
It also prevents the need to transitively import modules that
are not needed in the client.

>
> Mikhail Semenov
>
>> Subject: [c++std-ext-14850] Re: [modules] Draft of a module design
>> Date: Mon, 19 May 2014 20:47:42 -0700
>> From: Lawr...@Crowl.org
>> To: mod...@isocpp.org
>> CC: c++st...@accu.org
--
Lawrence Crowl

Richard Smith

unread,
May 20, 2014, 8:27:41 PM5/20/14
to mod...@isocpp.org, c++st...@accu.org
I'd be strongly opposed to such a requirement, since it prevents incremental refactoring. Consider:

Module A contains a bunch of functionality. You decide you want to factor out some of that functionality to another module. In the headers world, you can:
 - create a new header, b.h
 - move declarations to the new header, and include b.h in a.h
 - at whatever schedule suits you, move the users over to including b.h
 - finally (maybe when you bump your library's major version), remove the include of b.h from a.h.

Allowing a module to export the contents of another module allows this and other hierarchy refactoring tasks.

That said, I agree that the default should be for modules to not re-export their imports.

Lawrence Crowl

unread,
May 20, 2014, 8:32:43 PM5/20/14
to mod...@isocpp.org, c++st...@accu.org
Couldn't your scheme work as well with a module aggregator? That is,
a module consisting only of reexport declarations?

--
Lawrence Crowl

Richard Smith

unread,
May 20, 2014, 10:51:09 PM5/20/14
to mod...@isocpp.org, c++st...@accu.org
Wouldn't such a module violate the rule you were suggesting (that modules can't re-export the contents of other modules)? Or have I misunderstood?

I think that aggregation modules are nearly, but not quite sufficient, because I may not want to split module A into B and C; I will sometimes want to split A into A and B. (I would prefer to not need to transiently contrive another module for the stuff that remains in A, in case someone develops a dependency on it.)

Lawrence Crowl

unread,
May 20, 2014, 11:27:52 PM5/20/14
to mod...@isocpp.org, c++st...@accu.org
Well, it would be a slightly different beast in that it would only be
reexporting other modules. It in itself would have no symbols defined
within it.

> I think that aggregation modules are nearly, but not quite sufficient,
> because I may not want to split module A into B and C; I will sometimes
> want to split A into A and B. (I would prefer to not need to transiently
> contrive another module for the stuff that remains in A, in case someone
> develops a dependency on it.)

The aggregators would not do the A into A and B. I'm not convinced
that the loss there is less than the gain from avoiding excessive
exported symbols.

I suppose we could have two different export statments, one will full
indirection and one with the limited. The full export would then be
advertized as a refactoring aid and with the suggestion that new modules
not use it.

--
Lawrence Crowl

Cleiton Santoia

unread,
May 21, 2014, 5:33:59 PM5/21/14
to mod...@isocpp.org, c++st...@accu.org, lawr...@crowl.org
Actually, I prefer a fourth option:

(4)
module X1;
import X2; // import for use in defining the exported symbols
export { set_of_symbols... } // export symbols
export { import X3; } // illegal, reexport not permitted


May be a 5th option: specify which symbols we want to be imported, and transitively their dependencies.  

module x1{
 
export {
   
import x2;
   
class A;
   
class B;
 
}
};

using module x1::A;//pick just A and it's dependencies skips everything else

Lawrence Crowl

unread,
May 21, 2014, 5:43:09 PM5/21/14
to c++st...@accu.org, mod...@isocpp.org
On 5/21/14, Mikhail Semenov <mikhail...@hotmail.com> wrote:
> FOBIDDING SILECT INJECTION
> If we would like to forbid silent injections,
> we must provide the facility to export symbols:
>
> module X1;
> import X2;
> export { typename X2::T; void X2::g(int); }
> export { void f(T); }
> void f(T x){...}

Why do we need this? If the client wants to use f(T), it explicitly
imports both X1 and X2.

import X1;
import X2;
... { ... T v; f(v); ... }

That is, the clients dependence on X1 to get f and on X2 to get T is
directly reflected in the module import list.

If the client is not using T or f(T), it need not import X2,
but can still use other symbols from X1.

> AGGREGATION OF SYMBOLS
> We may allow to import namespaces, but it will be against the silent
> injection statement. We can have named exports:
>
> module X2;
> export A { typename T; void g(int); }
> export B { typename S; S p(const > S&); }...
> ---------------------------------------------------------
> module X1;
> import X2; export { X2::A; } // export only what is in export A
> export { void f(T); }
> void f(T x){...}

Richard made a good point about preserving the ability to refactor.
However, we should not necessarily use the same language feature to
support both refactoring and export control.

>> Actually, I prefer a fourth option:
>>
>> (4)
>> module X1;
>> import X2; // import for use in defining the exported symbols
>> export { set_of_symbols... } // export symbols
>> export { import X3; } // illegal, reexport not permitted
>>
>> So, given a type T in M1, the code
>>
>> module M2;
>> import M1;
>> export { void f(T); }
>>
>> would export f, but WOULD NOT export T. That is, a client of M2
>> must explicitly import M2 in order for f to be usable.
>>
>> This approach prevents silent injection of new symbols into the
>> clients namespace as the implementation of M2 evolves over time.
>> It also prevents the need to transitively import modules that
>> are not needed in the client.

--
Lawrence Crowl

Lawrence Crowl

unread,
May 21, 2014, 6:50:35 PM5/21/14
to mod...@isocpp.org, c++st...@accu.org
I don't think I understand the proposal. It looks like A is defined in x1.
What about the symbols in x2?

>
> --
> You received this message because you are subscribed to the Google Groups
> "SG2 - Modules" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to modules+u...@isocpp.org.
> To post to this group, send email to mod...@isocpp.org.
> Visit this group at http://groups.google.com/a/isocpp.org/group/modules/.
>


--
Lawrence Crowl

Cleiton Santoia

unread,
May 21, 2014, 7:07:55 PM5/21/14
to mod...@isocpp.org, c++st...@accu.org, lawr...@crowl.org


Em quarta-feira, 21 de maio de 2014 19h50min35s UTC-3, Lawrence Crowl escreveu:
> May be a 5th option: specify which symbols we want to be imported, and
> transitively their dependencies.
>
> module x1{
>   export {
>    import x2;
>    class A;
>    class B;
>   }
> };

I don't think I understand the proposal.  It looks like A is defined in x1.
What about the symbols in x2?
 

Yes, assuming A is defined in x1 and x1 is exporting it, and B and x2 symbols.
But we should import only A and what A depends, nothing else.

Let say in x1 we have this A  :
class A : public X {
 foo
(std::map<int, int>& m){...}
public:
 A
(const std::string& s) {...}
 A
(const std::vector<std::string>& v) {}
};

Here, when we "import x1::A;", we are importing A, and X and std::string and vector<std::string>, but not B nor anything else from X2, nor std::map ( because it´s private in A )

I don´t know exactly how to do this, but I think we can store in the compiled module, symbol by symbol, it´s direct dependencies and export only that.


Gabriel Dos Reis

unread,
May 21, 2014, 7:24:46 PM5/21/14
to mod...@isocpp.org, c++st...@accu.org
| -----Original Message-----
| From: lawrenc...@gmail.com [mailto:lawrenc...@gmail.com] On
| Behalf Of Lawrence Crowl
| Sent: Tuesday, May 20, 2014 5:26 PM
| To: c++st...@accu.org
| Cc: mod...@isocpp.org
C++ currently has 4 or 5 sets of name lookup rules.
Namespaces were specifically invented to address the name management problem.
I worry that inventing a sixth set of rules will only add to the existing problems.

When looking at this issue, we must remember that with module -- specifically, the ability to designate the set of symbols consumable from the outside -- the risk of name space pollution is significantly reduced since not everything that is defined in a module unit will be available to the outside, unlike header files. So our fear of name space pollution (with modules) might be out of proportion with what actually may happen. We definitely need more experience and practical data.

If the worry is about "stability", then fine-grained name management strikes me as the wrong direction to take, from experience with languages that provide such linguistic constructs. The reason is that every user of a module has to know the details, far more than she should, of a module's interface and essentially repeat the exports. If the imported module's (or one that it depends on) interface changes then potentially all users of that module have to go back to add or subtract from their imports/exports. That isn't stability when we are looking at programming in the large. With all the caveats that proof by analogy is fraud, we might want to reflect on our experience with exception-specification and how that turned out in practice.

With modern C++ it quite frequent to have header files, therefore their modularized versions, to put their declarations in namespaces. I would encourage use to leave scope management to namespaces than trying to fix them with modules.

-- Gaby

Gabriel Dos Reis

unread,
May 21, 2014, 7:32:20 PM5/21/14
to mod...@isocpp.org, c++st...@accu.org, lawr...@crowl.org

What about ADL? Should it find entities that are exported but not explicitly named in the import?  With CRTP, many of these entities aren’t reachable from usual name lookup rules.  This is slippery slope...

 

 

 

--

Richard Smith

unread,
May 21, 2014, 7:41:17 PM5/21/14
to mod...@isocpp.org, c++st...@accu.org, lawr...@crowl.org
On 21 May 2014 16:32, Gabriel Dos Reis <g...@microsoft.com> wrote:

What about ADL? Should it find entities that are exported but not explicitly named in the import?  With CRTP, many of these entities aren’t reachable from usual name lookup rules.  This is slippery slope...


I've already had to deal with ADL in Clang's prototype implementation. Our rules (which I think are probably the appropriate ones) work as follows:

 * Form a set of surrounding instantiations by following the rules in 14.6.4.1 [temp.point]. This set is the sequence of instantiations that are followed from a specialization to find its point of instantiation.
 * For a name to be visible to argument-dependent name lookup, it must be visible (within an associated namespace, per the non-modules ADL rules) within a module containing a member of the set of surrounding instantiations, or visible from the translation unit.

Essentially, the idea is to capture the notion that an instantiation can depend on names introduced anywhere in the minimal path of instantiations that leads to that instantiation being performed, but cannot depend on names that are declared anywhere else.

More naive rules (such as only looking in the module containing the template definition and in the translation unit) are non-composable and fail very quickly in real-world code.

Gabriel Dos Reis

unread,
May 21, 2014, 7:56:35 PM5/21/14
to <modules@isocpp.org>, c++st...@accu.org, lawr...@crowl.org


On May 21, 2014, at 4:41 PM, "'Richard Smith' via SG2 - Modules" <mod...@isocpp.org> wrote:

On 21 May 2014 16:32, Gabriel Dos Reis <g...@microsoft.com> wrote:

What about ADL? Should it find entities that are exported but not explicitly named in the import?  With CRTP, many of these entities aren’t reachable from usual name lookup rules.  This is slippery slope...


I've already had to deal with ADL in Clang's prototype implementation. Our rules (which I think are probably the appropriate ones) work as follows:

 * Form a set of surrounding instantiations by following the rules in 14.6.4.1 [temp.point]. This set is the sequence of instantiations that are followed from a specialization to find its point of instantiation.
 * For a name to be visible to argument-dependent name lookup, it must be visible (within an associated namespace, per the non-modules ADL rules) within a module containing a member of the set of surrounding instantiations, or visible from the translation unit.

Essentially, the idea is to capture the notion that an instantiation can depend on names introduced anywhere in the minimal path of instantiations that leads to that instantiation being performed, but cannot depend on names that are declared anywhere else.

That makes sense and is close to what we have in our model. (Points of instantiations are potential sources of new names/entities.)

I am unsure whether it continues to be unsurprising in a model where transitive imports are banned and users to explicitly import everything.  

More naive rules (such as only looking in the module containing the template definition and in the translation unit) are non-composable and fail very quickly in real-world code.

Many of the languages that require explicit import don't have ADL - well, only C++ and Axiom/Scratchpad are the languages I know that have ADL :-)

Cleiton Santoia

unread,
May 21, 2014, 9:22:09 PM5/21/14
to mod...@isocpp.org, c++st...@accu.org, lawr...@crowl.org, g...@microsoft.com


Em quarta-feira, 21 de maio de 2014 20h32min20s UTC-3, Gabriel Dos Reis escreveu:

What about ADL? Should it find entities that are exported but not explicitly named in the import? 


Yes... I think...
It´s better than import all exported symbols, isn´t ?
 

With CRTP, many of these entities aren’t reachable from usual name lookup rules. 


Yes, if you define 

x1.cpp
module x1 {
 
export {
     
class T;
     
class B;
     
void foo(X<T>);
 
}

   
template<class _Tp> class X {};
   
class T : public X<T> {};
   
void foo(X<T>) {}.
}

x2.cpp
module x2 {
   
import x1::T; // here we get T, X<T> and foo(X<T>)
}
 
At least we avoid to import B.

Thanks

Gabriel Dos Reis

unread,
May 21, 2014, 10:05:28 PM5/21/14
to mod...@isocpp.org, c++st...@accu.org, lawr...@crowl.org


On May 21, 2014, at 6:22 PM, "Cleiton Santoia" <cleiton...@gmail.com> wrote:



Em quarta-feira, 21 de maio de 2014 20h32min20s UTC-3, Gabriel Dos Reis escreveu:

What about ADL? Should it find entities that are exported but not explicitly named in the import? 


Yes... I think...
It´s better than import all exported symbols, isn´t ?

It is not clear why that is better - the user is still getting a symbol it didn't import contrary to the promise, but now she has to remember all the new lookup rules.

 

With CRTP, many of these entities aren’t reachable from usual name lookup rules. 


Yes, if you define 

x1.cpp
module x1 {
 
export {
     
class T;
     
class B;
     
void foo(X<T>);
 
}

   
template<class _Tp> class X {};
   
class T : public X<T> {};
   
void foo(X<T>) {}.
}

x2.cpp
module x2 {
   
import x1::T; // here we get T, X<T> and foo(X<T>)
}

So, one is getting things one didn't import explicitly - contrary to the belief that the explicit import gives you exactly what you want (and see in the import statement).

 
At least we avoid to import B.

Agreed, but is the result simpler?


Thanks

Maxim Kartashev

unread,
May 22, 2014, 4:39:32 AM5/22/14
to c++st...@accu.org, Francis Glassborow, mod...@isocpp.org


On 05/22/14 02:31 AM, Francis Glassborow wrote:
> I am trying to get to grips with this discussion.
>
> Let me summarise what seems useful to me:
>
> export lists which symbols in a module (however they get there) are
> visible outside the module
>
> import module A acquires all the names exported from A
> imnport x from A (or some syntax that effectively does that)
Except that current proposal does not provide any means to selectively
import stuff; it does provide a way to selectively export, though.

>

Cleiton Santoia

unread,
May 22, 2014, 1:46:27 PM5/22/14
to mod...@isocpp.org, c++st...@accu.org, lawr...@crowl.org, g...@microsoft.com
Sorry, I forgot rule 4 of the document, please let me try to understand first :

x0.cpp
module x0{
  export {
     class W { int w_mem; }
     class Z { int z_mem; }
  }
}


x1.cpp
module x1 {
  export {
    import x0;
    template<typename _Tp> class X { int m1; };
    class T : public X<T> { int m2; }
    class B { int m1; }
    void foo(const T& t) {}
  };

  void foo2(W w) {}
  void foo3(Z z) {}
}

main.cpp
import x1;  

int main() {
  T t;
  .
  .
  .
  return 0;
}

The instruction "import x1" in main imports :
  1 - All from x0, classes W and Z;
  2 - the template X;
  3 - two classes: T, and B;
  4 - the instantiation X<T> by paragraph 4.8.2.1 of the document; // please correct-me if I´m wrong
  4 - the function "foo";

Right?

But in main.cpp we use only T, no direct reference to B, or X, or W or Z or foo nor X<T>. Right ?

Gabriel Dos Reis

unread,
May 22, 2014, 2:11:10 PM5/22/14
to Cleiton Santoia, mod...@isocpp.org, c++st...@accu.org, lawr...@crowl.org
correct, the import makes available all the interface of x0 and the interface of x1.


But in main.cpp we use only T, no direct reference to B, or X, or W or Z or foo nor X<T>. Right ?


Correct.

I suspect only practical experience and hard data would tell us how much of a problem it is in practice - in a module world, not head files world.  Clearly, the alternative does not make available only things that are mentioned.

-- Gaby

Cleiton Santoia

unread,
May 23, 2014, 5:00:02 AM5/23/14
to mod...@isocpp.org, Cleiton Santoia, c++st...@accu.org, lawr...@crowl.org, g...@microsoft.com

But in main.cpp we use only T, no direct reference to B, or X, or W or Z or foo nor X<T>. Right ?

Correct.

I suspect only practical experience and hard data would tell us how much of a problem it is in practice - in a module world, not head files world.  Clearly, the alternative does not make available only things that are mentioned.

Sure practical experience is needed here, normally I invoke Java as a bad example, but Java import is able to import just one class, this is not exactly valid as an experience case, since naming lookup in C++ is much more complex, but I support the effort to overcome this problem, IMHO we can ask to compiler to import a class from a module, and do that without violating ADT nor any of the rules you propose in the paper. This will decrease significantly the number of symbols, just imagine when we put algorithms in a module, and you just want use std::copy nothing else... 


BR

indi.in....@gmail.com

unread,
May 31, 2014, 11:18:01 PM5/31/14
to mod...@isocpp.org, c++st...@accu.org
Might be too late to change before Rapperswil, but Rule 1 is "A translation unit may contain at most one module declaration...". Is this restriction necessary? I can think of several use cases where sharing code across several modules might be handy.

Allowing a TU to declare itself as part of more than one module doesn't really add all that much complexity either. You would just interpret:

    // ---------- file.cpp ----------
    module m1;
    module m2;
   
    export { /*exports*/ }
   
    /* module-private stuff */


as this:

    // ---------- logical-file1.cpp ----------
    module m1;
   
    export { /*exports*/ }
   
    /* module-private stuff */
   
    // ---------- logical-file2.cpp ----------
    module m2;
   
    export { /*exports*/ }
   
    /* module-private stuff */


where the stuff in the comments is the same.

The only additional complexity I can see this adding has to do with how to interpret something like this:

    module m1;
   
    import m1;


If you're allowing only one module per translation unit, you could simply consider this an error - it makes no sense. But you could also interpret it as a "do-nothing" statement - a statement with no effect. If you were allowing more than one module declaration per TU, the latter might be more useful. Then you could do:

    // ---------- file.cpp ----------
    module m1;
    module m2;
    
    import m1;


and have it interpreted as this:

    // ---------- logical-file1.cpp ----------
    module m1;
    
    import m1; // No effect
   
    // ---------- logical-file2.cpp ----------
    module m2;
    
    import m1;


So that m2 would import m1. This might be useful in some cases.

Of course, this:

    // ---------- file.cpp ----------
    module m1;
    module m2;
    
    import m1;
    import m2;


would lead to a circular dependency. Circular dependencies would need to be diagnosed and handled anyway; that mechanism would deal with this case, too. Note that it might be possible to import parts of each module into the other without introducing circular dependencies (though, it would be crazy) - the circular dependency detection mechanism would have to handle that kind of thing.

But whether or not you allow an import statement for (one of) the module(s) that a translation unit belongs to within that translation unit, the ability to declare that a translation unit belongs to multiple modules could be useful, while adding very little complexity.
Reply all
Reply to author
Forward
0 new messages