Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can I call function templates without forward declarations?

If a normal function calls a function that has not been declared yet, I get a compile-time error:

void foo(int x)
{
    bar(x);   // ERROR: bar has not been declared yet
}

void bar(int x)
{
    std::cout << x << '\n';
}

int main()
{
    foo(42);
}

The fix is to either forward-declare the called function, or to switch the order of definitions.

However, these fixes do not seem to be necessary with function templates:

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

template<typename T>
void bar(T x)
{
    std::cout << x << '\n';
}

int main()
{
    foo(42);
}

This compiles just fine. Why is that? When the compiler sees bar(x), why does it not complain?

(I am using g++ 4.6.3)

like image 856
fredoverflow Avatar asked Nov 29 '22 15:11

fredoverflow


2 Answers

This is a "why is the sky made out of bricks" type question. Ie, a question that asks why something false is true. It is not the case that in C++ your code is legal.

Live example, as you can see in gcc 4.8 this does not actually compile.

I guess the question "why does gcc 4.6 let this code compile" remains. One of the things that compilers did early on when writing template expanders was to treat them as something similar to macros. Very little would be done when they where declared, and everything would be looked up when they where instantiated.

Compilers now tend to do more thing when the template is declared, and less when it is instantiated. This is what the C++ standard requires, or is at least closer.

As it happens, ADL can get around this: bar lookups that find bar via ADL do not have to be visible at the point where foo is written, but rather at the point of instantiation.

The gcc 4.8 error message is pretty self explanatory:

prog.cpp: In instantiation of ‘void foo(T) [with T = int]’:
prog.cpp:16:7:   required from here
prog.cpp:6:10: error: ‘bar’ was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
     bar(x);   // OKAY
          ^
prog.cpp:10:6: note: ‘template<class T> void bar(T)’ declared here, later in the translation unit
 void bar(T x)
      ^

these requirements may have been changed or clarified in C++11, so it is possible that gcc 4.6's behavior was legal under the C++03 standard.

like image 139
Yakk - Adam Nevraumont Avatar answered Jan 25 '23 22:01

Yakk - Adam Nevraumont


When the compiler first sees bar(x), it doesn't know x's type, hence it can't look up the correct bar. Only when you instantiate foo, T and therefore x's type are known and bar(x) can be looked up.

Note that this work only for dependent expression, i.e. expressions that depend on a template parameter. If you add bar(42), it will fail to compile even if it is later instantiated with T==int.

You might also want to google "two-phase lookup" for further information. Only recent versions of GCC implement those rules correctly, as some checks also need to be done during the first phase of parsing the template. As pointer out by Yakk, newer versions of GCC reject your code, so always check with up-to-date versions of GCC or Clang to be on the safe side.

like image 31
Daniel Frey Avatar answered Jan 25 '23 23:01

Daniel Frey