5

I noticed strange behavior regarding function lookup when relying on a function to be defined later:

#include <iostream>

template <typename T>
void foo(const T&)
{
    std::cout << "Basic" << std::endl;
}

template <typename T>
void bar()
{
    T x;
    foo(x);
}

void foo(const int& x)
{
    std::cout << "int:" << x << std::endl;
}

int main()
{
    bar<int>();
}

Output:

Basic

For some reason, I expected the use of foo inside bar to find the overload below it. Moving the overload of foo to above bar makes the output the desired int:0 (or just writing a declaration).

This same behavior does not appear to apply to overloading a binary operator:

#include <iostream>

struct Foo {} foo;

template <typename T>
void operator<<(const Foo&, const T&)
{
    std::cout << "Basic" << std::endl;
}

template <typename T>
void bar()
{
    T x;
    foo << x;
}

void operator<<(const Foo&, const int& x)
{
    std::cout << "int:" << x << std::endl;
}

int main()
{
    bar<int>();
}

Output:

int:0

I have two questions, the first is: Why is the behavior like this and why is it different for operator overloading? The second is: If I have a named function (like my use of foo), is there a way to write a function bar in such a way to discover overloaded foos declared later in a translation unit?

Travis Gockel
  • 26,877
  • 14
  • 89
  • 116
  • I believe the second definition should *not* be found in either case. Function definitions not visible from the template declaration are only considered in case of ADL which does not happen here. – n. m. could be an AI Jul 02 '13 at 19:30
  • In the second case, `operator <<` is brought in through ADL because `Foo` brings in the `::` namespace. – Travis Gockel Jul 02 '13 at 20:06

1 Answers1

2

Welcome to the world of the most famous two phase lookup, and weirdo rules.

I'm sure there is no difference on operator and function cases just for the second you used one more argument. Try what happens if for the first version you also add another parameter with struct Foo...

Two phased lookup means that for dependent names when the template is compiled, it looks around and remember the set of visible functions. In your case that finds nothing. Then in the instantiation context there is another lookup, following the ADL (argument-dependent lookup) rules. That only. It means first collecting "associated namespaces" of the arguments, then look for more candidates in those namespaces.

In your case the only argument is int, and it has no associated namespaces, so nothing is found again. In the second case you also have Foo that drags :: with it, and your operator is found in ::.

Balog Pal
  • 16,195
  • 2
  • 23
  • 37
  • Well, that's complicated, but very good news for me: someone defining `struct Baz { friend void foo(const Baz& b); }` will have it work correctly, since ADL will find `foo(const Baz&)`. – Travis Gockel Jul 02 '13 at 19:19