9
namespace N {
    class C {};

    template<typename X>
    char const * found(X && x) {
        return "found";
    }

    template<typename, typename X>
    char const * notfound(X && x) {
        return "not found";
    }
}

This defines a namespace N with a class C and two function templates. found has a single template parameter, which can be deduced from the function argument. notfound has an additional template parameter which cannot be deduced.

Given following test code (on ideone):

#include <iostream>
int main() {
    N::C object;
    std::cout
        << found(object) << std::endl
        << notfound<bool>(object) << std::endl  // ERROR
        << notfound<bool, N::C>(object) << std::endl; // ERROR
}

I assumed that argument dependent lookup would find both found and notfound through the innermost enclosing namespace (which is N) of the argument type N::C.

However:

prog.cpp: In function ‘int main()’:
prog.cpp:21:6: error: ‘notfound’ was not declared in this scope
   << notfound<bool>(object) << std::endl
      ^~~~~~~~
prog.cpp:21:6: note: suggested alternative:
prog.cpp:12:15: note:   ‘N::notfound’
  char const * notfound(X && x) {
               ^~~~~~~~

(same error for notfound<bool, N::C>(object) after commenting out the notfound<bool>(object) call)

Why is notfound not found through ADL?


Background: I'm implementing a get function for some wrapper class, all in all relatively similar to std::get(std::tuple). The wrapper class, being an implementation detail, lives in some namespace lib::aspect::part::impl. I don't want users of the library to specify using lib::aspect::part::impl::get for obvious reasons.

Daniel Jour
  • 15,896
  • 2
  • 36
  • 63

1 Answers1

8

Because a function call to a function template with explicitly-specified template arguments requires the name of the template must be found by ordinary lookup; until that ADL can't kick in.

From the standard: $17.8.1/8 Explicit template argument specification [temp.arg.explicit]

(emphasis mine)

[ Note: For simple function names, argument dependent lookup 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 ([basic.lookup.unqual]). 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. [ Example:

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
}

— end example ] — end note ]

The last sentence gives a possible workaround; you can add the declaration of the function template anywhere to make the name visible for being called. e.g.

template<typename>
void notfound();

int main() {
    N::C object;
    std::cout
        << found(object) << std::endl
        << notfound<bool>(object) << std::endl
        << notfound<bool, N::C&>(object) << std::endl; // btw the 2nd template argument should be N::C&
}

LIVE

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • @DanielJour I argree that it's easy to overlook. And I found the explanations from the standard seems more clear; I revised my answer and hope it might help more. – songyuanyao Jun 12 '17 at 01:34
  • 1
    Part of the reason is because `<` is ambiguous; is it a template argument bracket or a less than? Suppose it was a `std::size_t` argument. `notfound<7>(object)` is `notfound<7` followed by that `>(object)` – Yakk - Adam Nevraumont Jun 12 '17 at 02:17
  • 2
    Wow, that helps a lot. I added a `template void notfound();` in the global namespace to the library. (non-deduced `typename..., typename` as template parameters should be impossible to instantiate IIRC) – Daniel Jour Jun 12 '17 at 07:19
  • @DanielJour That's [ill-formed](https://timsong-cpp.github.io/cppwp/temp.param#11.sentence-3), although currently I don't know of a compiler that diagnoses it. – T.C. Jun 12 '17 at 22:05