3

I'm solving a classical problem: check if there exists a free function in some namespace. It is discussed, e.g., here.

However, there is a slight twist: the definition of the function may appear later than the checker class. Here is an example.

struct Yes {};
struct No {};
struct YesButLater {};

void f(Yes) {}

template<typename T, typename Enable = void>
struct HasF : public std::false_type {};

template<typename T>
struct HasF<T, decltype(void( ::f(T()) ))> : public std::true_type {};

void f(YesButLater) {}

int main() {
    cout << HasF<Yes>::value << endl; // 1
    cout << HasF<No>::value << endl; // 0
    cout << HasF<YesButLater>::value << endl; // 0, expected 1
}

f(YesButLater) is declared later than HasF helper class, and, although I instantiate the template after f(YesButLater) was defined, the helper doesn't notice it.

So, here is question 1: how can I deal with it?

Now one more, more curious, example.

template<typename T>
struct HasF<T, decltype(void( f(T()) ))> : public std::true_type {};

void f(YesButLater) {}
void f(std::string) {}

int main() {
    cout << HasF<YesButLater>::value << endl; // 1 (but what's the difference?)
    cout << HasF<std::string>::value << endl; // 0, expected 1
}

Note that I removed :: from the decltype(...) expression. Now for some reason f(YesButLater) is noticed by HasF, but f(std::string) still remains obscure.

Question 2: Why do we observe different behaviour for ::f(T()) and f(T()) in this example? Moreover, what is the difference between YesButLater and std::string?

I think that there is some trick with namespace lookup but I cannot get the thing.

Community
  • 1
  • 1
Ivan Smirnov
  • 4,365
  • 19
  • 30
  • my guess - it is due to [adl](http://en.cppreference.com/w/cpp/language/adl) rules... – W.F. Mar 16 '17 at 12:12
  • @W.F. Yes, probably, though I still don't understand why :: matters as I always use global namespace in the example. – Ivan Smirnov Mar 16 '17 at 12:15

1 Answers1

3

It seems that I've figured out what is going on.

When I write ::f(...), the name f is being searched with a qualified name lookup. This kind of lookup encounters only names which declarations were available up to the point of call. Now it is clear why the first version couldn't find f(YesButLater): its declaration occurs later.

When I write f(...), unqualified name lookup happens. Again, it fails to find any name which was declared before calling point. Here argument dependent lookup comes up. It searches for f(T) in the whole namespace which T belongs to. In case of f(YesButLater) this namespace is global, hence the function is found. In case of f(std::string) ADL tries searches for std::f(std::string) and, of course, fails.

Here are two examples to illustrate the case.

namespace foo {
    class C {};
}

template<typename T>
void call() {
    f(T());
}

namespace foo {
    void f(C) {}
}

int main() {
    call<foo::C>();
}

Here f(T()) is being searched with ADL and is found, although its declaration is after call(). And if we modify the call() function...

template<typename T>
void call() {
    foo::f(T());
}

This results in compilation error because foo::f(T) performs qualified lookup and cannot find the needed function as no declaration is available at the moment.

Ivan Smirnov
  • 4,365
  • 19
  • 30