简体   繁体   中英

Why does overloaded function template order matter for fundamental types?

For illustration purposes, say I have this contrived container:

template <typename T>
struct Container {
    T v;
};

And this contrived "value:"

struct Value {};

I have a recursive templated function declared exactly like so:

template <typename T>
void visit(const Container<T> &c) {
    visit(c.v);
}

template <typename T>
void visit(const T &v) {}

With the above, it is perfectly valid to invoke visit(Value{}) . I can also invoke:

void this_works_too(Container<Value> c) {
    visit(c);
}

This is a little puzzling, because the recursive call visit(cv) occurs before its definition.

But, when we try the following, we get an error (both from clang 6 and gcc 8):

void this_blows_up(Container<int> c) {
    visit(c);
}

It complains about the recursive call visit(cv) now:

error: call to function 'visit' that is neither visible in the
       template definition nor found by argument dependent lookup

However, if we reorder the declarations of visit :

template <typename T>
void visit(const T &c) {}

template <typename T>
void visit(const Container<T> &c) {
    visit(c.v);
}

Both, this_works_too and this_blows_up compile, successfully.

(This behavior also happens for STL containers and is irrespective of the const and reference qualifiers)

Why does the order become important for specializing visit(Container<int>) when it wasn't for visit(Container<Value>) ?

In my research, I had a suspicion that this had to deal with ADL (which I admit I do not fully understand). But my best interpretation of this is, at the end of searching in the case of Container<int> , the set should have found int due to ordinary unqualified lookup . So, both Container<int> and Container<Value> should work.

Godbolt demonstrating this

I have confirmed that despite the reordering, the correct overload is called (ie. when visit(T) is first, visit(Container<T>) is actually invoked for visit(Container<int>) instead of just choosing the more generic visit(T) ). I believe this is because the most specific specialization is chosen.

In my research, I had a suspicion that this had to deal with ADL (which I admit I do not fully understand).

You're right that this is the problem.

But my best interpretation of this is, at the end of searching in the case of Container<int> , the set should have found int due to ordinary unqualified lookup.

Ordinary unqualified lookup cannot see the template <typename T> void visit(const T &v) overload because it hasn't been declared yet. Argument-dependent lookup is an exception to that; it can see all declarations at the point of instantiation.

The type int is a built-in type not related to any particular namespace. Because int does not have any associated namespaces, there are no namespaces in which ADL searches for declarations, so there is no way for the second declaration to be found.

The type Value is a user-defined type in the global namespace. Because of that, ADL searches in the global namespace for declarations, and in that case, it does find the second declaration.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM