2

The following code compiles even though nonexisting_func() doesn't exist. The code compiles because it's a member function of a templated class, but the function itself is not compiled because it's not being used in the program, correct? So you could have any syntax errors inside heapify_down and the entire code should still compile?

#include <iostream>
#include <vector>

template<typename T>
class heap
{
public:
   void heapify_down(std::vector<T> &vec)
   {
      nonexisting_func(vec);
   }
};

int main( ) 
{ 
   heap<int> my_heap;
   return 0;
}

If my understanding above is correct, then why does the following code not compile?

#include <iostream>
#include <vector>

template<typename T>
class heap
{
public:
   void heapify_down(std::vector<T> &vec)
   {
      a;
      nonexisting_func(vec);
   }
};

int main( ) 
{ 
   heap<int> my_heap;
   return 0;
}

Compiling this code gives me the error error: use of undeclared identifier 'a'. Why is it trying to compile the heapify_down() function now?

24n8
  • 1,898
  • 1
  • 12
  • 25
  • Are you looking for a language-lawyer type answer, or a practical one? This behavior unfortunately tends to be different based on which compiler you use... –  Feb 06 '20 at 19:27
  • `a;` on its own is nonsense. To declare a variable you need 1) a type. 2) a variable name. See something missing? – Jesper Juhl Feb 06 '20 at 19:29
  • @JesperJuhl That's the point of the question. OP is purposefully putting in errors into the function. They are asking why the error comes up even if the function isn't used. – François Andrieux Feb 06 '20 at 19:30
  • @Frank Could you provide both answers? I don't quite understand this behavior – 24n8 Feb 06 '20 at 19:30
  • @FrançoisAndrieux Right. Is `a;` any more nonsense than calling a function that doesn't exist? – 24n8 Feb 06 '20 at 19:30
  • @François Because the program needs to be syntactically valid, regardless of what parts of it are used, of course. – Jesper Juhl Feb 06 '20 at 19:33
  • @JesperJuhl Why is `a;` less syntactically valid than `nonexisting_func(vec);`? – Kevin Feb 06 '20 at 19:33

2 Answers2

4

Template code basically has two passes that it goes through. The first pass just looks at the code and makes sure that it is syntactically correct, and that any non-dependent code is correct. By non-dependent code, I mean code that does not depend on the template parameter(s).

After that there is a second pass that happens after the template is actually instantiated. At that point there is no more dependent code as all the template parameters are known and the compiler can exam the code like it would for any non-template code.

In

void heapify_down(std::vector<T> &vec)
{
    nonexisting_func(vec);
}

The call to nonexisting_func depends on vec because of ADL and vec depends on T so it's compilation is deferred. It is syntactically correct so it wont have any further checking done until it is instantiated. If you changed main to

int main( ) 
{ 
   std::vector<int> foo;
   heap<int> my_heap;
   my_heap.heapify_down(foo);
   return 0;
}

So that heapify_down is actually instantiated then you would get a compiler error like

main.cpp:22:7: error: use of undeclared identifier 'nonexisting_func'
      nonexisting_func(vec);
      ^
main.cpp:30:12: note: in instantiation of member function 'heap<int>::heapify_down' requested here
   my_heap.heapify_down(foo);
           ^
1 error generated.

You would also get an error using

void heapify_down(std::vector<T> &vec)
{
    ::nonexisting_func(vec);
}

Because now we are no longer have an unqualified name so ADL is ignored meaning ::nonexisting_func is no longer a dependent name.

With

void heapify_down(std::vector<T> &vec)
{
   a;
   nonexisting_func(vec);
}

a doesn't depend on T so the compiler tries to look it up. It can't find it, so you get an error. If you had instead done

void heapify_down(std::vector<T> &vec)
{
   this->a;
   nonexisting_func(vec);
}

then again you would not get an error until you instantiate the function since a now depends on this and this depends on T.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Question regarding your last sentence. You say that `this` depends on `T`. But isn't `T` is known through `heap my_heap;`? – 24n8 Feb 06 '20 at 19:41
  • @Iamanon `T` is known when you do `heap my_heap;` but before that it is not so the compiler has to wait for the second pass (after you do `heap my_heap;`) before it actually can check what `this` refers to. – NathanOliver Feb 06 '20 at 19:44
  • @Iamanon And even then, unless you use `heapify_down` the compiler can defer instantiating and checking it. – NathanOliver Feb 06 '20 at 19:49
  • 1
    It might be useful to note that `nonexisting_func(vec);` depends on `T` because of ADL, and `::nonexisting_func(vec);` fails to compile – Artyer Feb 06 '20 at 19:56
3

The key point here is that nonexisting_func(vec); is going to mean something different depending on what T is (since vec is a vector<T>).

So in that case, the compiler cannot try to resolve the call to nonexisting_func() immediately as it lexes the function, and can only do so when comes the time to instantiate the template (which you never do).

In the case of a, since that identifier doesn't depend on anything, the compiler should perform the lookup immediately. I say should because MSVC in particular tends to still delay the lookup until instantiation, even though it shouldn't on paper.

  • ahhh that makes sense. I also had tested calling `nonexisting_func()` without an argument that has a template dependency, and in that case, the code failed to compile. – 24n8 Feb 06 '20 at 19:35
  • Wait, what do you mean by "instantiate the template?" Doesn't `heap my_heap;` instantiate the template? – 24n8 Feb 06 '20 at 19:38
  • 1
    @lamanon it instantiates the class, meaning all the declarations have to be valid, but it doesn't instantiate definitions for member functions that you never call. In your first example if you called `my_heap.heapify_down({});` from main, it would fail to compile – Ryan Haining Feb 06 '20 at 19:40
  • A compiler is not required to perform the lookup immediately. The standard requires an implementation to issue a diagnostic. It doesn't stipulate *when* in the process of translating source code that it should issue that diagnostic. – Peter Feb 06 '20 at 19:41
  • @Peter The problem is that MSVC does not **ever** issue any diagnostics for non-dependant lookup failure for never instantiated templates. –  Feb 06 '20 at 19:43
  • 1
    @Frank FWIW, GCC and clang do the same thing. Untill the template code is actually instantiated, the compiler can defer fully checking it. – NathanOliver Feb 06 '20 at 19:45
  • @Frank - Quite a few compilers do that since, by the way they work, they don't have information needed to issue a diagnostic until the template is instantiated. The general rule of thumb is that, if you want to be sure the compiler will issue diagnostics, you write code (e.g. test cases) that implicitly or explicitly instantiates the template with representative template parameters. – Peter Feb 07 '20 at 12:51