Non pre-declared function call works for class types but not primitive types
Asked Answered
U

2

13

In the following code

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

class Something {};
void bar(Something) {}

int main() {
    foo(Something{});
}

(https://wandbox.org/permlink/l2hxdZofLjZUoH4q)

When we call foo() with a Something parameter, everything works as expected, the call dispatches to the bar(Something) overload.

But when I change the argument to an integer and provide a bar(int) overload, I get an error

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

void bar(int) {}

int main() {
    foo(int{});
}

Error:

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

(https://wandbox.org/permlink/GI6wGlJYxGO4svEI)

In the class case, I have not defined bar() in a namespace along with the definition of Something. Meaning that I am not getting ADL. Then why does the code work with class types?

Unpen answered 18/8, 2018 at 5:21 Comment(1)
Global namespace counts as a namespace for the purposes of ADL.Certification
P
7

Then why does the code work with class types?

According to §6.4.2/2.1:

The sets of namespaces and classes are determined in the following way:

  • If T is a fundamental type, its associated sets of namespaces and classes are both empty.

So upon writing foo(int), the compiler will have an empty set of namespaces and classes to be considered. The call to bar thus must fail, as it is not declared yet. If you declare foo(int) on beforehand, your code will compile:

void bar(int);

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

void bar(int) {}

int main() {
    foo(int{});
}

On the other hand, in the case of the foo(Something), the (global) namespace will be part of the lookup, so the compiler actively scans the namespace for a function named bar that can be called with Something instances.

Physicality answered 18/8, 2018 at 6:55 Comment(0)
D
4

Inside foo definition, bar is a dependent name because it is called with an argument that depends on a template parameter(T).

Dependent name resolution is performed twice [temp.dep.res]:

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 ([temp.point]) and from the definition context. Bellow, comments show where is the point of instantiation:

template <typename T>
void foo(T) {  //point of definition of foo
    bar(T{});
}

class Something {};
void bar(Something) {}
void bar(int) {}

int main() {
    foo(int{});
    foo(Something{});
}
//point of instantiation of foo<int>
//point of instantiation of foo<Something>

For foo<Something> and foo<int> no bar are visible from the point of definition.

For foo<Something>, Something being a class its associated namespace is the namesapce in which it is declared: the global namespace. According to the second bullet, name look up is performed in the global namespace from the point of instantiation and bar(Something) is found.

For foo<int>, int is a fundamental type and it has not any associated namespaces. So no name look up is performed from the point of instantiation, and bar<int> is thus not found.

Discussant answered 18/8, 2018 at 7:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.