Question

I am attempting to call an overloaded function inside a namespace and am struggling a bit.

Working Example 1: No namespace

class C {};

inline void overloaded(int) {}

template<typename T> void try_it(T value) {
  overloaded(value);
}

inline void overloaded(C) {}


int main()
{
  try_it(1);
  C c;
  try_it(c);
  return 0;
}

Working Example 2: All overloads defined before template

class C {};

namespace n {
  inline void overloaded(int) {}
  inline void overloaded(C) {}
}

template<typename T> void try_it(T value) {
  n::overloaded(value);
}

int main()
{
  try_it(1);
  C c;
  try_it(c);
  return 0;
}

Broken Example 3: Some overloads after the template

class C {};

namespace n {
  inline void overloaded(int) {}
}

template<typename T> void try_it(T value) {
  n::overloaded(value);
}

namespace n {
  inline void overloaded(C) {}
}

int main()
{
  try_it(1);
  C c;
  try_it(c); // /tmp/test.cpp: In function ‘void try_it(T) [with T = C]’:
             // /tmp/test.cpp:19:15:   instantiated from here
             // /tmp/test.cpp:8:7: error: cannot convert ‘C’ to ‘int’ for argument ‘1’ to ‘void n::overloaded(int)’

  return 0;
}

Why is this the case? What do I need to do to be able to declare or define an overload after the template function?

Was it helpful?

Solution

This is a problem of dependent name lookup.

In the expression overloaded(value);, the name overloaded is dependent as per [temp.dep]/1.

As far as I know, in the expression n::overloaded(value), the name overloaded (in the id-expression n::overloaded) is not dependent.


Dependent name lookup is very peculiar, [temp.dep.res]/1

In resolving dependent names, names from the following sources are considered:

  • Declarations that are visible at the point of definition of the template.
  • Declarations from namespaces associated with the types of the function arguments both from the instantiation context and from the definition context.

(There's a point of instantiation for function templates at the end of the file, so all declarations from associated namespaces can be found.)

For non-dependent names, the normal lookup rules apply (lookup from the definition context).

Therefore, to find names declared after the definition of the template, they have to be dependent and be found via ADL.


A simple workaround is to introduce another parameter to the function overloaded or wrap the argument, such that one of the arguments of this function has namespace n associated:

#include <iostream>

class C {};

namespace n {
  struct ADL_helper {};
  inline void overloaded(int, ADL_helper = {})
  { std::cout << "n::overloaded(int,..)" << std::endl; }
}

template<typename T> void try_it(T value) {
  overloaded(value, n::ADL_helper{});
}

namespace n {
  inline void overloaded(C, ADL_helper = {})
  { std::cout << "n::overloaded(C,..)" << std::endl; }
}

int main()
{
  try_it(1);
  C c;
  try_it(c);
}

alternatively:

namespace n {
  template < typename T >
  struct wrapper { T elem; };

  inline void overloaded(wrapper<int>)
  { std::cout << "n::overloaded(wrapper<int>)" << std::endl; }
}

template<typename T> void try_it(T value) {
  overloaded(n::wrapper<T>{value});
}

namespace n {
  inline void overloaded(wrapper<C>)
  { std::cout << "n::overloaded(wrapper<C>)" << std::endl; }
}

OTHER TIPS

HUGE EDIT from original version where I mistakenly thought that the call was a non-dependent name.

OK, let's try to break this down.

The second example works, because even in the non-template case you'd expect things to work normally with all the overloads declared before the use.

The first version works because of 14.6.4.2/1:

For a function call that depends on a template parameter, if the function name is an unqualified-id but not a templateid, the candidate functions are found using the usual lookup rules (3.4.1, 3.4.2) except that:

— For the part of the lookup using unqualified name lookup (3.4.1), only function declarations with external linkage from the template definition context are found.

— For the part of the lookup using associated namespaces (3.4.2), only function declarations with external linkage found in either the template definition context or the template instantiation context are found.

We're interested specifically in the first part if the function name is an unqualified-id and the second bullet found in either the template definition context or the template instantiation context are found. So we learn that if the name is unqualified, names visible at thepoint of instantiation are added to the set of candidates.

Likewise in the third case, your name is fully-qualified, thus inhibiting the use of candidates visible at the point of instantiation and instead falling back only on candidates at the point of definition.

If you were to move c into n, add using namespace n; into the try function, and remove the n:: from the function call you would find that ADL would once again pick up the overload and all would be happy.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top