Search

1.7 — Forward declarations and definitions

Take a look at this seemingly innocent sample program called add.cpp:

You would expect this program to produce the result:

The sum of 3 and 4 is: 7

But in fact, it doesn’t compile at all! Visual Studio 2005 Express produces the following compile errors:

add.cpp(5) : error C3861: 'add': identifier not found
add.cpp(9) : error C2365: 'add' : redefinition; previous definition was 'formerly unknown identifier'

The reason this program doesn’t compile is because the compiler reads files sequentially. When the compiler reaches the function call to add() on line 5 of main(), it doesn’t know what add is, because we haven’t defined add() until line 9! That produces the first error (“identifier not found”).

When Visual Studio 2005 gets to the actual declaration of add() on line 9, it also complains about add being redefined. This is somewhat misleading, given that it wasn’t ever defined in the first place. Later versions of Visual Studio correctly omit this additional error message.

Despite the redundancy of the second error, it’s useful to note that it is fairly common for a single error to produce (often redundant) multiple compiler errors or warnings.

Rule: When addressing compile errors in your programs, always resolve the first error produced first.

To fix this problem, we need to address the fact that the compiler doesn’t know what add is. There are two common ways to address the issue.

Option 1: Reorder the function calls so add() is defined before main():

That way, by the time main() calls add(), the compiler will already know what add() is. Because this is such a simple program, this change is relatively easy to do. However, in a larger program, it can be tedious trying to figure out which functions call which other functions (and in what order) so they can be declared sequentially.

Furthermore, this option is not always possible. Let’s say we’re writing a program that has two functions A and B. If function A calls function B, and function B calls function A, then there’s no way to order the functions in a way that they will both be happy. If you define A first, the compiler will complain it doesn’t know what B is. If you define B first, the compiler will complain that it doesn’t know what A is.

Function prototypes and forward declaration of functions

Option 2: Use a forward declaration.

A forward declaration allows us to tell the compiler about the existence of an identifier before actually defining the identifier.

In the case of functions, this allows us to tell the compiler about the existence of a function before we define the function’s body. This way, when the compiler encounters a call to the function, it’ll understand that we’re making a function call, and can check to ensure we’re calling the function correctly, even if it doesn’t yet know how or where the function is defined.

To write a forward declaration for a function, we use a declaration statement called a function prototype. The function prototype consists of the function’s return type, name, parameters, but no function body (the part between the curly braces). And because the function prototype is a statement, it ends with a semicolon.

Here’s a function prototype for the add() function:

Now, here’s our original program that didn’t compile, using a function prototype as a forward declaration for function add():

Now when the compiler reaches add() in main, it will know what add() looks like (a function that takes two integer parameters and returns an integer), and it won’t complain.

It is worth noting that function prototypes do not need to specify the names of the parameters. In the above code, you can also forward declare your function like this:

However, we prefer to name our parameters (using the same names as the actual function), because it allows you to understand what the function parameters are just by looking at the prototype. Otherwise, you’ll have to locate the function definition.

Tip: You can easily create function prototypes by using copy/paste on your function declaration. Don’t forget the semicolon on the end.

Forgetting the function body

One question many new programmers have is: what happens if we forward declare a function but do not define it?

The answer is: it depends. If a forward declaration is made, but the function is never called, the program will compile and run fine. However, if a forward declaration is made, the function is called, but the program never defines the function, the program will compile okay, but the linker will complain that it can’t resolve the function call.

Consider the following program:

In this program, we forward declare add(), and we call add(), but we never define add() anywhere. When we try and compile this program, Visual Studio 2005 Express produces the following message:

Compiling...
add.cpp
Linking...
add.obj : error LNK2001: unresolved external symbol "int __cdecl add(int,int)" (?add@@YAHHH@Z)
add.exe : fatal error LNK1120: 1 unresolved externals

As you can see, the program compiled okay, but it failed at the link stage because int add(int, int) was never defined.

Other types of forward declarations

Forward declarations are most often used with functions. However, forward declarations can also be used with other identifiers in C++, such as variables and user-defined types. Other types of identifiers (e.g. user-defined types) have a different syntax for forward declaration.

We’ll talk more about how to forward declare other types of identifiers in future lessons.

Declarations vs. definitions

In C++, you’ll often hear the words “declaration” and “definition” used. What do they mean? You now have enough of a framework to understand the difference between the two.

A definition actually implements or instantiates (causes memory to be allocated for) the identifier. Here are some examples of definitions:

A definition is needed to satisfy the linker. If you use an identifier without providing a definition, the linker will error.

The one definition rule (or ODR for short) is a well-known rule in C++. The ODR has two parts:
1) Within a given file, an identifier can only have one definition.
2) Within a given program, an object or normal function can only have one definition. This distinction is made because programs can have more than one file (we’ll cover this in the next lesson). Note that some other types of identifiers (such as types, template functions, and inline functions) are exempt from this rule (we haven’t covered what these are yet, so don’t worry about this for now -- we’ll bring it back up when it’s relevant).

Violating the ODR will generally cause the compiler or linker to issue a redefinition error, even if the definitions are identical.

A declaration is a statement that tells the compiler about the existence of an identifier (variable or function name) and its type. Here are some examples of declarations:

A declaration is all that is needed to satisfy the compiler. This is why using a forward declaration is enough to keep the compiler happy. If you use an identifier without providing a declaration, the compiler will error.

You’ll note that “int x” appears in both categories. In C++, all definitions also serve as declarations. Since “int x” is a definition, it’s by default a declaration too. Therefore, in many cases, we only need a definition. However, if you need to use an identifier before it’s defined, then you’ll need to provide an explicit declaration. This is why our use of a forward declaration in the above example is necessary.

There is a small subset of declarations that are not definitions, such as function prototypes. These are called pure declarations. Other types of pure declarations include forward declarations for variables, class declarations, and type declarations (you will encounter these in future lessons, but don’t need to worry about them now). You can have as many pure declarations for an identifier as you desire (although having more than one is redundant).

Quiz

1) What’s the difference between a function prototype and a forward declaration?

2) Write the function prototype for this function:

3) For each of the following programs, state whether they fail to compile, fail to link, or compile and link. If you are not sure, try compiling them!

4)

5)

6)

Quiz Answers
1) Show Solution

2) Show Solution

3) Show Solution

4) Show Solution

5) Show Solution

6) Show Solution

1.8 -- Programs with multiple files
Index
1.6 -- Whitespace and basic formatting

226 comments to 1.7 — Forward declarations and definitions

  • Aditya

    Hey Alex!
    Thanks for the correction.
    Now, I want this code to calculate in decimals. That is,if i say 35/45,i want  to get the answer in decimals.
    How do i do that?

    • Alex

      If you use type "double" instead of "int", you can use numbers with decimals (e.g. 1.45). Handling fractions (e.g. 35/45) is a lot more complicated and far beyond the scope of these introductory lessons.

      I do show examples of a Fraction class in chapter 8 or 9.

  • aditya

    What is wrong with this code?

  • Diego Sandoval

    You say that a function can have only one definition, yet this code compiles and executes correctly. both functions are called (depending on the arguments provided). I didn't understand the rule correctly? right?

    • nascardriver

      Hi Diego!

      Those are two different functions which share the same name (Function overloading).
      The one definition rule is only valid for the same function header (Same return-type, same name, same parameters).

      References
      * Lesson 7.6 - Function overloading

  • Hi, couldn't all the forward definitions just be put together in a header file such as <<forward.h>> and precompiled with a #include call at the top such as

    ?

    • nascardriver

      Hi Nigel!

      Yes, that's how it's usually done, because it allows usage of the functions from any file. Nothing is being compiled though, only the contents will be copied.

      • thanks, just getting my head around why we were looking at "global" scoped definitions in the program that just make the listing look untidy. Are headers covered later?

        • nascardriver

          Yep, starting with the next lesson.
          The code on learncpp is single-file in most cases, because it's easier to understand that way. In your projects, you should use multiple files.

  • jft

    This is always a definition as a memory location is allocated for x (which is uninitialized).

    This is a declaration as no memory is allocated for x and x here refers to x defined in another translation unit.

  • Zane

    Why does this work?

    I ask, because the definition of add() is after that of the body of main(). If the the program is read/ran sequentially, when would it ever encounter the definition? Shouldn't the definition come before main()? If not, I am missing something. As I understand it, at the return of a value for main() the program stops, never having made it to/read add().

    • nascardriver

      Hi Zane!

      Your compiler compiles all functions, no matter where they are. To call a function, all your compiler needs to know, is the function declaration, not the definition. After compilation the order of functions doesn't matter, because your computer can call functions above and below the calling function.

  • Deepesh Choudhary

    Okay, so a declaration is used just to satisfy the compiler.
    Does that mean that the declaration statement does not get converted to any machine code?
    Does that also mean that if main.o is reversed, there won't be any code/statements indicating such declaration?

    • nascardriver

      Hi Deepesh!

      Yes to both.
      The compiler will generate the function at address Y. Your computer isn't restricted to calling functions that have been declared before the current point of execution. It can just from address X to address Y without worries.

  • Pork and Beans

    Hello Alex!

    I hope you are gearing up for a stellar weekend.

    I also hope that you do, in fact, want me to point out any mistakes I see.

    In the paragraph above "Quiz", there is a mistake.

    "Others types of pure declarations..." should be "Other".

    I thought you would want to know.

    Thank you as always for your incredible work.

    Have a nice day.

  • Farzad.S

    I see, by Forward declarations and definitions, we can call the functions in any order we want. Am I right?

  • Farzad.S

    Hello, I did understand the Forward declarations and prototype. But I am curious why do we need it, we can just use function and keep calling it. I believe it makes the program more complicated.

    By the way, for ages, I was trying to learn C++ but couldn't find the right book or courses to clearly teaching me from scratch. Great job. thanks a lot

    • nascardriver

      Hi Farzad!

      > I believe it makes the program more complicated
      It sure does, but there are situations where it cannot be avoided.

      This program will just run an infinite loop, but there are cases in which a construction like this is required.

    • vbot

      My preferred programing-flow is linear from top to bottom, because I'm used to languages where the order of function definitions and calls doesn't matter:

      so this is where I see the need for forward declarations.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]