C++ Logo

std-proposals

Advanced search

Re: Type dependent namespace and symbols set control

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Fri, 18 Dec 2020 20:22:27 -0500
On Thu, Dec 17, 2020 at 5:07 PM Jean-Baptiste Vallon Hoarau via
Std-Proposals <std-proposals_at_[hidden]> wrote:
>
> Recently i've been pondering on ADL, customization points and interface
adaptation quite a bit.
> (TLDR there's a paper embedded in this email that try to address these
problems, let me know what you think)

I've read your attached paper, "Type-namespace and symbols set control." I
think:
(1) It's got way too many new features bundled together in one paper. Are
they all needed, or are you sneaking in some "nice-to-haves" among the
"must-haves"? Which are which?
(2) The features are poorly motivated. I mostly understand what each of
them does, physically, but I don't understand why I as a C++ programmer
would want to do those things. I recommend using "Tony Tables" to show (on
the left) today's C++20 code that accomplishes some realistic task, and
then (on the right) how the same task would be solved in a simpler or more
robust way using your new features. The important thing in a Tony Table is
that the left-hand column must be real code that people are writing today.
If your proposal simplifies the writing of only some pieces of code that
people don't actually want to write, then your proposal doesn't help.
(3) I don't see a migration path from "C++20 ADL" to "your thing." IIUC,
you are essentially proposing to take today's working code

    namespace N {
        struct A {};
        void swap(A&, A&);
    } // namespace N

    void test() {
        N::A a, b;
        using std::swap;
        swap(a, b); // should call N::swap, not std::swap
    }

and silently break it. You write:
"In order for a function F to be implicitly included in the set of
available functions for unqualified look-up, F must be available in the
associated anonymous type-namespaces of the types of the arguments."
So, either you are proposing a rule by which N::swap becomes "available in
the associated anonymous type-namespace" of N::A, or else you're proposing
to break this existing code. I don't see the former; I suspect the latter,
which is a non-starter for WG21.

Longer/nittier comments:

(4) Your moo(cow) example in section 1.1 is isomorphic to
https://quuxplusone.github.io/blog/2018/08/13/fixing-adl-field-test/#update-tomasz-kami-ski-points-ou
No less a personage than Herb Sutter has also proposed breaking this code,
in P0934R0 "A Modest Proposal: Fixing ADL." But, if you're proposing to
break it, you should clearly and explicitly say so, and show this example
(or something like it). Your current example with names like `my_type` and
`moo` is too contrived.

(5) Also in that example, you say "Under the current ADL rules..." but
that's meaningless, as your example code uses syntax that is not C++20
syntax — `template<class T> namespace <my_type<T>>` and so on. So there's
no way of telling what the "current" rules would make of it.

(6) You write: "An anonymous type-namespace can be extended, unless
declared `final`." This is meaningless. Namespaces can't be declared
`final` today, and I don't see any grammar in your proposal that would make
it possible in the future. For another thing, namespaces are open by
definition; suppose I have two translation units, and in one I define
`namespace A final { int foo(); }` and in the other I define `namespace A {
int bar(); }`. Neither TU is aware of the other. Are you proposing that
this should just be IFNDR?

(7) In section 1.2's example, I recommend getting rid of the functions'
bodies. Just put their declarations:
    template <class T>
    virtual namespace plot_traits {
        Point2D get_plot_point(T&, int);
        int get_num_points(T&);
    }
    template <class T>
    namespace complex_plot final : override
plot_traits<std::vector<std::complex<T>>> {
        Point2D get_plot_point(T&, int);
        // OK as plot_traits<...> inside a plot_traits override always
refer to the primary namespace
        using plot_traits<std::vector<std::complex<T>>>::get_num_points;
    }
Incidentally, I've moved the `final` keyword to a more appropriate place (I
assume that's what you intended).

(8) Your code comment there is completely at odds with how templates work
anywhere else in the language. Normally, when I have a template with some
partial specializations, the partial specializations are there specifically
to *avoid* instantiating the primary template for some problematic type.
You seem to be proposing that when plot_traits<X> refers to plot_traits<X>,
the compiler should take that as a sort of "super class reference," and go
and instantiate the *next most specialized* specialization of the template.
What happens if there are multiple partial specializations available, and
it's not clear which one is the next most specialized?

(9) Finally, where did the identifier `complex_plot` spring from? What is
its purpose? What is the relationship between this "specialization" of
namespace plot_traits<T> and the actual namespace complex_plot?
    namespace complex_plot { int baz(); }

(10) The punchline of section 1.2 seems to be that after jumping through
all of these hoops, we can then use plot_traits<T>::get_num_points(t)
exactly as if plot_traits were defined as a plain old traits class, like
`iterator_traits` or `regex_traits`:

    template<class T>
    struct plot_traits;

So what's the purpose of all this new syntax? What does it buy us?
Certainly not simplicity!

(11) In section 1.3, you should read
https://brevzin.github.io/c++/2019/04/13/ufcs-history/ and then eliminate
section 1.3.

(12) Section 2.1, "not using", is interesting.
Do you understand the subtle difference between "using N::foo;" and "using
namespace N;"? (I owe a blog post on this subject, although honestly I
don't understand the reason it was done this way to begin with. Asked on
SO:
https://stackoverflow.com/questions/65365681/why-does-cs-using-namespace-work-the-way-it-does
)
What should the effect of "not using namespace N;" be, if namespace N
hadn't been "using-ed" to begin with?

(13) Section 2.2 lacks motivation. Remember, "motivation" isn't "Without
this feature, my sample code won't compile!" and it isn't "This keyword
kind of looks like it might do X, so let's make it do that." You should
write a Tony Table, showing some real code from your codebase on the left,
and then showing how you want to be able to write it, on the right. Make
sure the snippet is long enough that it's "falsifiable." You want the
reader to be able to say, "Ah, yes, I *agree* that that is the best
possible way to write that code in C++20, and I could not do better myself;
and furthermore, I agree that the right-hand snippet is better." If your
snippet is too short and contrived, the reader will simply *disagree* that
the left-hand snippet is reasonable.

(14) Section 2.3, I don't understand how this `namespace(T)` relates back
to your original idea of having an "anonymous associated namespace" named
`namespace<T>`. Are `namespace(T)` and `namespace<T>` the same thing? If
so, pick one name and stick to it. Don't make two names for the same notion.

(15) Section 2.4 lacks motivation, and also lacks rigor — what do you mean
by "prioritization" in this context? (But first, write a Tony Table! If you
can't do that, then there's no point trying to explain what you meant by
"prioritization.")

(16) Section 2.4 also seems to be assuming some kind of UFCS. In C++ today,
`t.blah()` never calls a free function.

(17) Section 2.5 lacks motivation.
See D&E section 17.4, where Stroustrup writes:
> ...the original design allowed for several member names to be mentioned
in a single using-declaration:
> using X::(f,g,h);
> This is syntactically ugly, and so were all the alternatives we
considered. ... Having used namespaces
> a bit, I found far less need for such lists than I had expected. I also
tended to overlook such lists when
> reading code because they resembled function declarations too much, so I
fell into the habit of using
> repeated using-declarations instead:
> using X::f;
> using X::g;
> using X::h;
> Consequently, there is no special form of a using-declaration that
specifies a list of member names.

I buy Stroustrup's logic.


Bottom line: I'm glad you're aware that lookup in C++ is a big mess with no
logic. But it seems like you're just throwing a grab-bag of additional
widgets at the wall, without actually trying to solve any existing problem.
*And* you're proposing silent code breakage with no migration path.

HTH,
Arthur

Received on 2020-12-18 19:22:41