Why doesn't ADL find function templates?
Asked Answered
M

4

88

What part of the C++ specification restricts argument dependent lookup from finding function templates in the set of associated namespaces? In other words, why does the last call in main below fail to compile?

namespace ns {
    struct foo {};
    template<int i> void frob(foo const&) {}
    void non_template(foo const&) {}
}

int main() {
    ns::foo f;
    non_template(f); // This is fine.
    frob<0>(f); // This is not.
}
Mima answered 1/6, 2010 at 22:0 Comment(7)
Does it mean, that you expect to work frob() without writing ns::frob()?Yaupon
Yes, in the manner of a non-template function.Mima
FYI the above code fails in Comeau as well: comeaucomputing.com/tryitout -- adding using namespace ns; or the ns:: qualification passes compilation. This is a good question.Fye
Another data point: For template<typename T> void bar(T) {}, ADL works fine, bar(f) succeeds.Panzer
@Huw: just bitten by it :) Funny how explicit qualification rules ADL out I guess :/Oyer
@Matt: Haha, and me too just now. Small programming world.Stone
It works now in C++20; thanks to P0846.Herra
B
91

This part explains it:

C++ Standard 03 14.8.1.6:

[Note: For simple function names, argument dependent lookup (3.4.2) applies even when the function name is not visible within the scope of the call. This is because the call still has the syntactic form of a function call (3.4.1). But when a function template with explicit template arguments is used, the call does not have the correct syntactic form unless there is a function template with that name visible at the point of the call. If no such name is visible, the call is not syntactically well-formed and argument-dependent lookup does not apply. If some such name is visible, argument dependent lookup applies and additional function templates may be found in other namespaces.

namespace A {
  struct B { };
  template<int X> void f(B);
}
namespace C {
  template<class T> void f(T t);
}
void g(A::B b) {
  f<3>(b);    //ill-formed: not a function call
  A::f<3>(b); //well-formed
  C::f<3>(b); //ill-formed; argument dependent lookup
              // applies only to unqualified names
  using C::f;
  f<3>(b);    //well-formed because C::f is visible; then
              // A::f is found by argument dependent lookup
}
Bally answered 1/6, 2010 at 22:18 Comment(8)
Ah, i couldn't find that reference :) +1Zymotic
Yeah, it's a note, so it's not easily findable :/Bally
What's the rationale for this? Seems like a strange requirement. I mean, what does the syntactic form have to do with anything?Lila
@LightnessRacesinOrbit Section 9.3.5 in Vandevoorde & Josuttis explains why this is a syntactic problem (naming adopted to OP's example): "a compiler cannot decide that f<3>(b) is a function call argument until it has decided that <3> is a template argument list. Conversely, we cannot decide that <3> is a template argument list until we have found f() to be a template. Because this chicken and egg problem cannot be resolved, the expression is parsed as (f<3)>(b), which makes no sense." Note that this is similar to the template disambiguation syntax for member function templates.Spent
Are there any proposal to fix this issue? template f<3>(b) may be a better syntax?Whew
@Spent In the OPs code where the line : non_template(f); can be argued with same theory provided in your comment i.e a compiler cannot decide that "non_template_function(b)" is a function call, until it's decided "b" is function call argument, which can't be decided until we have found non_template_function(). Because of this chicken and egg problem call shouldn't have resolved...Because
@AngelusMortis expression ( expressions... ) (note lack of operator before the parenthesis) is always a function call, though, and the compiler knows that as soon as it sees the open parenthesis. The problem here is that < can serve both as an operator and as the start of a template argument list, and the compiler has to do a lot of extra parsing to figure out which (and possibly there are some arrangements of code where this is not possible to do unambiguously). It appears the standard authors elected to make that illegal, perhaps to save the hair of compiler developers.Kolivas
This requirement has been lifted in C++20 and OP's code is now well-formed :)Kenogenesis
G
12

Since c++20, adl works also fine with explicit function template. Here is the proposal: P0846R0: ADL and Function Templates that are not Visible:

Instead of requiring the user to use the template keyword, a revision to the lookup rules was proposed so that a name for which a normal lookup produces either no result or finds one or more functions and that is followed by a a "<" would treated as if a function template name had been found and would cause ADL to be performed.

Currently, only GCC 9 has implement this feature, so your example can compile.

live demo.

Glimmer answered 30/12, 2018 at 2:37 Comment(1)
Excellent! Now working in Clang and elsewhere.Herra
U
5

I would like to refine slightly accepted answer. It is not clear in the OP question, but the important part from the standard (cited by Kornel) is this (emphasis mine):

But when a function template with explicit template arguments is used, the call does not have the correct syntactic form

so what is prohibited is relying on ADL and using explicit template arguments. Unfortunately using non-type template arguments requires using explicit arguments (unless they have default values).

Below is sample code showing this.:

[live]

#include <string>
#include <utility>

namespace C {
  struct B { };
  template<class T> void f(T t){}
}

void g(C::B b) {
  f(b);           // OK
  //f<C::B>(b);   // ill-formed: not a function call, but only 
                  //  because explicit template argument were used

  std::string s;
  move(s);                      // OK
  //move<std::string&>(s);      // Error, again because 
                                //  explicit template argument were used
  std::move<std::string&>(s);   // Ok
}

int main()
{
 C::B b;
 g(b);
}
Unlearned answered 30/8, 2016 at 9:5 Comment(0)
C
0

Edit: No, this is not right. See @Kornel's answer.


I'm not entirely sure but having consulted Stroustrup's "The C++ programming language" I think that Appendix C section 13.8.4 might be the cause.

Since frob is a template one could conceivably specialise it for i=0 at a point after you call it. This means that the implementation would be left with two possible ways of choosing which frob to call as it appears it can choose it at the point of instantiation or at the end of processing the translation unit.

So, I think the problem is you could do

namespace ns {
    struct foo {};
    template<int i> void frob(foo const&) {}
}

int main() {
    ns::foo f;
    frob<0>(f);
    return 0;
}

namespace ns {
    template<> void frob< 0 >(foo const&) { /* Do something different*/ }
}
Cognoscenti answered 1/6, 2010 at 22:40 Comment(2)
Nope, take of the namespaces and you still have your problem, don't you? The specialization after usage is a normal problem in C++, the specialized form isn't used if declared after.Bally
@Kornel: Ah yes, that gives a different error, one more in line with what I described. Fair enough, thanks for pointing that out.Cognoscenti

© 2022 - 2024 — McMap. All rights reserved.