C++ Logo

std-proposals

Advanced search

Re: [std-proposals] [DRAFT PAPER] Allowing the establishment of namespace scopes in an export-declaration

From: Jason McKesson <jmckesson_at_[hidden]>
Date: Wed, 14 Sep 2022 12:01:32 -0400
On Wed, Sep 14, 2022 at 1:20 AM Zopolis0 via Std-Proposals
<std-proposals_at_[hidden]> wrote:
>
> Fair enough. I didn't fully consider the implications. Given what you have raised, I am going to initiate a sick flip in the aisle and now advocate that namespaces can be declared in module interface partitions. Any partition can declare any namespace, and they will all behave as normal.
>
> Normal, for clarity, is as such where:
>
>
> export module bar namespace foo;
>
> export int frob
> {
> return 1;
> }
>
> int strob
> {
> return 2;
> }
>
> is equivalent to
> export module bar;
>
> namespace foo
> {
> export int frob
> {
> return 1;
> }
> }
>
> int strob
> {
> return 2;
> }
>
> Does that make sense? Where exported names have an implied namespace block wrapping around them?

So again, we circle back around to "what is this feature, actually?"
Because the way you keep describing it, you are declaring that the
exported names are a member of the namespace, and unexported names are
not. But you never seem to contend with the *conseqences* of this
dichotomy.

If `frob` is a member of `foo` (rather than simply being accessible
through `foo`), then that means `foo::frob` is the proper name of that
entity. If `strob` is not a member of `foo`, then `::strob` is the
proper name of that entity. Which means that the definition of `strob`
is not within `foo`. This means that the definition of `strob`
*cannot* use unqualified names to call exported entities. If `strob`
were defined as `return frob()`, that would be a compile error. If
`strob` wants to call this function, it *must* use the fully qualified
name `foo::frob`.

As must every other usage of any exported entity that is itself not
within an `export`ed definition. This includes *implementation* units.
So if you want to implement `frob` in an implementation unit, it must
declare it as `int foo::frob()`. And it must *not* declare `strob` as
`int foo::strob()`, despite the fact that they are both declared in
the *exact same visual scope* in the source code.

Similarly, if `frob` wants to call `strob`, it *must not* qualify that
name with `foo::`. It must either use unqualified lookup or the fully
qualified `::strob` name.

So what is your intent here? Should `strob`'s implementation be able
to use the unqualified name `frob` to refer to `foo::frob`? Or should
it be able to use the unqualified name only if the definition is in
the interface file that exported `frob`? Or should all uses of it need
full qualification?

And what of argument-dependent lookup? Consider this:

```
export module bar namespace foo;

export struct T{};

void adl(T &t);
```

By your transformation, you get this:

```
export module bar;

namespace foo
{
  export struct T{};
}

void adl(T &t);
```

Which means if there is any code in the implementation that has a `T`
and wants to use an unqualified call `adl(t)`, that doesn't work
anymore. `adl` namespace is not associated with `T`'s namespace. It
would only work if `adl` is exported.

But the reason for that is not visually present in the actual
definition of `adl` or `T`. It's some esoteric interaction that
happened because of something at the top of the file.

Is breaking ADL in these cases something that you *intend* to do? Do
you genuinely intend for two declarations side-by-side that are
*visually* at the same scope to not actually be in the same scope?

Received on 2022-09-14 16:02:28