This is an archive of the discontinued LLVM Phabricator instance.

[Sema] Add more tests for the behavior of argument-dependent name lookup
ClosedPublic

Authored by riccibruno on Apr 11 2019, 11:02 AM.

Details

Summary

The goal here is to exercise each rule in [basic.lookup.argdep] at least once. These new tests expose what I believe are 2 issues:

  1. CWG 1691 needs to be implemented (p2: [...] Its associated namespaces are the innermost enclosing namespaces of its associated classes [...]) The corresponding tests are adl_class_type::X2 and adl_class_type::X5.
  1. The end of paragraph 2 ([...] Additionally, if the aforementioned set of overloaded functions is named with a template-id, its associated classes and namespaces also include those of its type template-arguments and its template template-arguments.) is not implemented. Closely related, the restriction on non-dependent parameter types in this same paragraph needs to be removed. The corresponding tests are in adl_overload_set. (both issues are from CWG 997).

Diff Detail

Repository
rL LLVM

Event Timeline

riccibruno created this revision.Apr 11 2019, 11:02 AM

Also test for inline namespaces.

test/CXX/basic/basic.lookup/basic.lookup.argdep/p2-associated-namespaces-classes.cpp
304 ↗(On Diff #194763)

I see how g3 matches the example in CWG997
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#997
However, I don't see how CWG997's resolution actually affected this example in the slightest. The wording inserted for CWG997 was, "Additionally, if the aforementioned set of overloaded functions is named with a template-id, its associated classes and namespaces are those of its type template-arguments and its template template-arguments." That makes e.g.

f(g3<N::S>)

consider N::f, because N::S is a "type template-argument" of the template-id g3<N::S> which names the set of overloaded functions. But it doesn't do anything at all to f(g3) because g3 is not a template-id and doesn't have any template-arguments.

This piece of ADL is implemented only by GCC (not EDG, Clang, or MSVC), and personally I would very much like to keep it that way. We know there's no real-world code that expects or relies on CWG997 — because such code would never work in practice except on GCC. Let's keep it that way! As soon as you implement a crazy arcane rule, such that code _could_ portably rely on it, code _will start_ relying on it... and then we'll never be able to simplify the language.
Case in point: the piece of ADL described in this blog post --
https://quuxplusone.github.io/blog/2019/04/09/adl-insanity-round-2/
As soon as the above-described arcane ADL rule was implemented in GCC and Clang, Boost.Hana started relying on it; and now the rule is "locked in" to the paper standard because there's real-world code relying on it.
Personally I'd like to _keep_ real-world code from relying on CWG997, until someone figures out what CWG was thinking when they added it.

riccibruno marked an inline comment as done.Apr 12 2019, 7:15 AM
riccibruno added inline comments.
test/CXX/basic/basic.lookup/basic.lookup.argdep/p2-associated-namespaces-classes.cpp
304 ↗(On Diff #194763)

I think that the relevant part of CWG 997 is the removal of the restriction on non-dependent parameter types. Sure, g3 is not a template-id, but it refers to an overload set which contains the second g3, and one of the parameter of this second g3 is N::Q<T>.

I don't think this is a surprising rule. It matches the general intuition that for function types ADL is done based on the function parameter types and return type. Not having this rule introduces a difference between function templates and functions in overload sets. Consider https://godbolt.org/z/UXHqm2 :

namespace N {
    struct S1 {};
    template <typename> struct S2 {};

    void f(void (*g)());
}

void g1();          // #1
void g1(N::S1);     // #2

void g2();                                  // #3
template <typename T> void g2(N::S2<T>);    // #4

void test() {
    f(g1); // ok, g1 is #1
    f(g2); // should be ok, g2 is #3
}
test/CXX/basic/basic.lookup/basic.lookup.argdep/p2-associated-namespaces-classes.cpp
77 ↗(On Diff #194763)

Do you also have a test somewhere to verify that the parameter and return types of a lambda's operator() do not contribute to the associated types of the lambda type itself? That is,

// https://godbolt.org/z/g_oMOA
namespace N {
    struct A {};
    template<class T> constexpr int f(T) { return 1; }
}

constexpr int f(N::A (*)()) { return 2; }
constexpr int f(void (*)(N::A)) { return 3; }

void test() {
    constexpr auto lambda = []() -> N::A { return {}; };
    static_assert(f(lambda) == 2);

    constexpr auto lambda2 = [](N::A) {};
    static_assert(f(lambda2) == 3);
}

Clang does handle this correctly; I'm just asking for it to be tested, if it's not already. (I might have overlooked an existing test.)

144 ↗(On Diff #194763)

I think for completeness there should be a "negative test" for non-type template arguments:

namespace X4 {
  template <auto NT> struct C {};
  namespace N {
    struct Z {
        enum E { E0 };
        void X4_f(C<E::E0>);
    };
    enum E { E0 };
    void X4_g(C<E::E0>);
  }
}
void test4() {
  X4::C<X4::N::E::E0> c1;
  X4::C<X4::N::Z::E::E0> c2;
  X4_f(c1); // expected-error{{undeclared identifier 'X4_f'}}
  X4_g(c2); // expected-error{{undeclared identifier 'X4_g'}}
}

In C++2a, user-defined NTTPs will become possible, so we'll want another test for something like

// https://godbolt.org/z/MfWG8C
namespace X4 {
  template<auto NT> struct C {};
  namespace N {
    struct Z {
      int i;
      constexpr Z(int i): i(i) {}
      auto operator<=>(const Z&) const = default;
    };
    void X4_f(C<Z(0)>);
  }
}
void test4() {
  X4::C<X4::N::Z(0)> c1;
  X4_f(c1); // expected-error{{undeclared identifier 'X4_f'}}
}
304 ↗(On Diff #194763)

I think that the relevant part of CWG 997 is the removal of the restriction on non-dependent parameter types.

Ah, I had missed the removal of the word (non-dependent) in my reading of CWG997. So just that one-word removal is what fixed their example, and is what you're testing with g3.

I still object to g2 — I would like that FIXME to say PLEASEDONTFIXME or something. :)

test/CXX/basic/basic.lookup/basic.lookup.argdep/p3.cpp
50 ↗(On Diff #194763)

...But if you put the using M::f; _after_ the extern void f(char);, then GCC believes it's okay. https://godbolt.org/z/DghSTM
You should definitely have a test for the using-after-extern case, just to make sure it doesn't ICE or anything.

test/CXX/basic/basic.lookup/basic.lookup.argdep/p4.cpp
96 ↗(On Diff #194763)

Nit: 80-column lines here and above would be nice. :)

As you're making tests for ADL corner cases, you might also consider testing the interactions between ADL and defaulted function parameters, e.g. https://godbolt.org/z/vHnyFl
It looks like everyone (except MSVC) already gets that stuff right (or at least portable-between-the-big-three). I bet the behavior naturally falls out of some other rules; you might say "there's no way that could possibly break, so we don't need to test it," and I'd accept that.

riccibruno marked 11 inline comments as done.Apr 14 2019, 6:03 AM

As you're making tests for ADL corner cases, you might also consider testing the interactions between ADL and defaulted function parameters, e.g. https://godbolt.org/z/vHnyFl
It looks like everyone (except MSVC) already gets that stuff right (or at least portable-between-the-big-three). I bet the behavior naturally falls out of some other rules; you might say "there's no way that could possibly break, so we don't need to test it," and I'd accept that.

I think that as you say this falls out of the other rules.

test/CXX/basic/basic.lookup/basic.lookup.argdep/p2-associated-namespaces-classes.cpp
77 ↗(On Diff #194763)

I am not sure, but I see no harm in adding it here.

144 ↗(On Diff #194763)

Isn't that already covered by the test in adl_class_template_specialization_type::X1 ? I agree that a test for class type NTTP will be needed in C++2a, but for now this feature is not yet implemented.

304 ↗(On Diff #194763)

I don't understand your objection to the template-id rule of CWG 997 (which is what f(g2<N::S>) tests). It seems to me that the following should be well-formed (https://godbolt.org/z/J5uhQ-) :

namespace N {
    template <typename T> void f(T &&);
    struct S {};
}

template <typename T> struct C {};
template <typename T> void g();

void test() {
    f(C<N::S>{}); // ok
    f(g<N::S>);   // should be ok (and is ok according to the spec)
}
test/CXX/basic/basic.lookup/basic.lookup.argdep/p3.cpp
50 ↗(On Diff #194763)

Hmm... I am not sure what gcc is thinking here. The order should not matter according to the beginning of [basic.lookup.argdep]p3.

test/CXX/basic/basic.lookup/basic.lookup.argdep/p4.cpp
96 ↗(On Diff #194763)

I am usually a little bit more flexible on the 80-columns rule in tests, but you are right. Fixed.

riccibruno marked 3 inline comments as done.

Also test that typedef-names and using-declarations are not considered.

test/CXX/basic/basic.lookup/basic.lookup.argdep/p2-associated-namespaces-classes.cpp
304 ↗(On Diff #194763)

I admit that your comparison makes the rule look reasonable. :) But here are some comparisons designed to make the rule look UNreasonable.
https://godbolt.org/z/n5kvUE

namespace N {
    void foo(void(*)());

    class T {};
    using A = int;
    template<class> class TT {};
    template<class> using AT = int;
    void F();
    void (*V)();
}

template<class> void ft_t();
template<class> void (*vt_t)();
template<auto&> void ft_nt();
template<auto&> void (*vt_nt)();
template<template<class> class> void ft_tt();
template<template<class> class> void (*vt_tt)();

void test() {
    foo(ft_t<N::T>);  // OK: ADL considers N::foo (nobody but GCC implements this)
    foo(vt_t<N::T>);  // Error: ADL does not consider N::foo
    foo(ft_t<N::A>);  // Error: ADL does not consider N::foo
    foo(vt_t<N::A>);  // Error: ADL does not consider N::foo

    foo(ft_nt<N::V>);  // Error: ADL does not consider N::foo
    foo(vt_nt<N::V>);  // Error: ADL does not consider N::foo
    foo(vt_nt<N::F>);  // Error: ADL does not consider N::foo

    foo(ft_tt<N::TT>);  // OK: ADL considers N::foo (nobody but GCC implements this)
    foo(vt_tt<N::TT>);  // Error: ADL does not consider N::foo
    foo(ft_tt<N::AT>);  // OK: ADL considers N::foo (nobody but GCC implements this)

    foo( (ft_t<N::T>) );  // OK??: ADL considers N::foo (nobody but GCC implements this)
    foo( &ft_t<N::T>  );  // OK??: ADL considers N::foo (nobody but GCC implements this)
    foo( (ft_tt<N::TT>) );  // OK??: ADL considers N::foo (nobody but GCC implements this)
    foo( &ft_tt<N::TT>  );  // OK??: ADL considers N::foo (nobody but GCC implements this)
    foo( (ft_tt<N::AT>) );  // OK??: ADL considers N::foo (nobody but GCC implements this)
    foo( &ft_tt<N::AT>  );  // OK??: ADL considers N::foo (nobody but GCC implements this)
}

In all of these cases there is no ADL, on any of the Big 4 compilers, except for GCC in some of the cases. So it would be really nice IMO to just say "okay, let's just strike those corner cases from C++2b, eliminate these cases of ADL from GCC, the world gets a little bit simpler, no code breaks, everyone is happy." If Clang moves closer to GCC (and further from EDG/MSVC), then the happy path actually becomes harder to sell.

(I'm pretty sure the cases I marked OK?? are indeed OK according to the paper standard; I think that's why [basic.lookup.argdep] says "...if the argument is the name or address of a set of overloaded functions...")

riccibruno marked 2 inline comments as done.Apr 15 2019, 6:23 AM

(I have numbered your examples to make the discussion easier, and moved to a non-inline comment to have more space)

foo(ft_t<N::T>);  // OK: ADL considers N::foo (nobody but GCC implements this) #1
foo(vt_t<N::T>);  // Error: ADL does not consider N::foo #2
foo(ft_t<N::A>);  // Error: ADL does not consider N::foo #3
foo(vt_t<N::A>);  // Error: ADL does not consider N::foo #4

foo(ft_nt<N::V>);  // Error: ADL does not consider N::foo #5
foo(vt_nt<N::V>);  // Error: ADL does not consider N::foo #6
foo(vt_nt<N::F>);  // Error: ADL does not consider N::foo #7

foo(ft_tt<N::TT>);  // OK: ADL considers N::foo (nobody but GCC implements this) #8
foo(vt_tt<N::TT>);  // Error: ADL does not consider N::foo #9
foo(ft_tt<N::AT>);  // OK: ADL considers N::foo (nobody but GCC implements this) #10

foo( (ft_t<N::T>) );  // OK??: ADL considers N::foo (nobody but GCC implements this) #11
foo( &ft_t<N::T>  );  // OK??: ADL considers N::foo (nobody but GCC implements this) #12
foo( (ft_tt<N::TT>) );  // OK??: ADL considers N::foo (nobody but GCC implements this) #13
foo( &ft_tt<N::TT>  );  // OK??: ADL considers N::foo (nobody but GCC implements this) #14
foo( (ft_tt<N::AT>) );  // OK??: ADL considers N::foo (nobody but GCC implements this) #15
foo( &ft_tt<N::AT>  );  // OK??: ADL considers N::foo (nobody but GCC implements this) #16

I am sympathetic to your concerns about having consistent rules for ADL in these examples. However I am not sure that the conclusion here is to ban them.

First I believe that the examples #11-#16 are just variations of the previous examples because of [over.over]p1: The overloaded function name can be preceded by the & operator. [...].

The examples involving a variable template are currently not supported, but it may be just because it is omitted from the list of ADL rules ? It might be worth filing an issue asking for clarification about this case. WDYT ? (In general I have noticed that variable templates are rarely mentioned in the standard).

The examples #3, #4 are a consequence of the fact that builtin types have no associated namespaces and classes. But this is something that is consistent; just use a class type instead if ADL is intended to be used. Similarly the examples #5. #6, and #7 are consistent; for now, don't rely on ADL with NTTPs (similarly perhaps this case should also be included in the ADL rules since C++20 will allow class types as NTTPs ? I did not see any discussion about this issue in P0732r2.)

@rsmith @aaron.ballman Do you have any opinion on whether the ADL rules from CWG 997 should be implemented ? The issue here as I understand it is that these rules expand ADL, but no one apart from GCC implement them. By implementing these rules, the argument to remove them in the future becomes weaker since real code might actually start to depend on them.

@Quuxplusone Do you have other objections apart from the template-id issue ? If not, since D60573 depends on this patch, I would like to commit this with a comment explaining the issue instead of the FIXME.

@Quuxplusone Do you have other objections apart from the template-id issue ? If not, since D60573 depends on this patch, I would like to commit this with a comment explaining the issue instead of the FIXME.

Sure, go for it!

riccibruno accepted this revision.Apr 21 2019, 5:09 PM

@Quuxplusone Do you have other objections apart from the template-id issue ? If not, since D60573 depends on this patch, I would like to commit this with a comment explaining the issue instead of the FIXME.

Sure, go for it!

Thanks a lot for your insightful comments and for your time, I really appreciate it!

This revision is now accepted and ready to land.Apr 21 2019, 5:09 PM
This revision was automatically updated to reflect the committed changes.
Herald added a project: Restricted Project. · View Herald TranscriptApr 22 2019, 4:39 AM