8.1 — Introduction to compound data types

In chapter 4, we introduced fundamental data types, which are the basic data types that C++ provides as part of the core language.

We’ve made much use of these fundamental types in our programs so far, especially the int data type. And while these fundamental types are extremely useful for straightforward uses, they don’t cover our full range of needs as we begin to do more complicated things.

For example, imagine you were writing a math program to multiply two fractions. How would you represent a fraction in your program? You might use a pair of integers (one for the numerator, one for the denominator), like this:

But this introduces a couple of challenges. First, each pair of integers is only loosely linked -- outside of comments and the context of how it’s used in the code, there’s little to suggest that each pair of variables is related somehow. Second, following the DRY (don’t repeat yourself) principle, we probably should put the code that allows the user to input a fraction in a function (along with some error handling). But if we did this, how would this function pass both integers back to the caller since functions can only return a single value?

Alternately, what if you were writing a program that needed to keep a list of employee IDs? You might try something like this:

But what if you had 100 employees? Would you hand-type 100 variable names? And what if you needed to print them all? Or pass them to a function? Oh my, we’d be in for a lot of typing. This simply doesn’t scale.

Clearly fundamental types will only carry us so far.

Compound data types

Fortunately, C++ supports an entire second set of data types called compound data types. Compound data types (also sometimes called composite data types) are data types that can be constructed from fundamental data types (or other compound data types). Many compound data types have their own unique properties as well.

With a compound data type, we could create a new type (named Fraction) that consists of two integers, and then use that new Fraction type in our program wherever we need a fraction. We could also, in a single statement, create a list to hold all our employee IDs and initialize it.

The different compound types

C++ supports the following compound types:

  • Functions
  • Arrays
  • Pointers
  • Pointer-to-member
  • L-value references
  • R-value references
  • Unscoped and scoped enumerations
  • Structs and classes
  • Unions

Although you probably didn’t know it was a compound data type, you’ve already been using functions regularly. For example, consider this function:

The type of this function is actually void(int, double), which is composed of fundamental types! Of course, functions also have their own special behaviors as well (e.g. being callable).

Compound types are really the heart and soul of C++, and most of the standard library consists of two compound types: functions and classes. As an example, our old buddies std::cout and std::cin are class type objects.

In this chapter, we’ll cover some of the easier compound types, including unscoped enumerations, scoped enumerations, l-value references, pointers, and we’ll introduce structs. Next chapter, we’ll introduce arrays and discuss how to use class types. Beyond that, much of the remaining chapters will be devoted to designing your own class types.

Program-defined types

When we want a variable of a fundamental type (or some of the compound types, such as pointers and references), we can just define a variable of that type, because the types themselves have already been defined for us (as part of the core C++ language):

However, some of the compound types (enumerations, unions, structs, and classes) bring one other neat ability to the table: the ability to create new types for use in our own programs! Such types are called program-defined types. For example, we could create a new type named Fraction that contained two integers (named numerator and denominator), and then use it in our programs:

Ignoring what a struct is for the moment, you can see that we’re defining what a Fraction looks like (in the global scope, so it can be used anywhere in the rest of the file), and then we’re later instantiating a variable of that type (inside main()).

This aspect of program-defined types sometimes confuses new programmers -- much like we have to define a function before we can call it, with program-defined types, we have to tell the compiler how the type is defined before we can instantiate variables using that type. We’ll see more examples of defining and using program-defined types in the next lesson (8.2 -- Introduction to unscoped enumerations).

By convention, program-defined types are named starting with a capital letter and don’t have a “_t” suffix (e.g. Fraction, not fraction or fraction_t), primarily to help differentiate them from variable names (which start with a lower case letter by convention).

Best practice

Whenever you create a new program-defined type, name it starting with a capital letter

Using program-defined types throughout a multi-file program

In lesson 2.11 -- Header files, we discussed how we could use header files to propagate function forward declarations so functions defined in one code file can be called from another code file.

Similarly, if we want to use a type definition in multiple files, the type definition needs to be included in each file that uses it. To make this easy, type definitions are often placed directly in header files for easy inclusion in all of the code files that need those type definitions.



For advanced readers

In lesson 2.7 -- Forward declarations and definitions, we discussed how the one-definition rule required us to define functions (and variables) in code files (not headers), and propagate only the forward declarations via header files.

When instantiating variables of a program-defined type, the compiler must be able to see the full type definition (not just a forward declaration). As a result, program-defined types are exempted from the one-definition rule, so the definitions can be placed directly in the header files and included wherever the full definition is needed.

User-defined types

The term user-defined type sometimes comes up in casual conversation, as well as being mentioned (but not defined) in the C++ language standard. In casual conversation, the term tends to mean “a type that you defined yourself” (in other words, a program-defined types). Sometimes it also includes type aliases.

However, more formally (as used in the C++ language standard, and as advocated for by Bjarne Stroustrup), a user-defined type is actually any compound type, even those provided as part of the C++ standard library (which isn’t user-defined at all!).

As a result, we’ll generally avoid the term user-defined type.

8.2 -- Introduction to unscoped enumerations
7.x -- Chapter 7 summary and quiz


For example:

Ignoring what a struct is for the moment, you can see that we’re defining what a Fraction looks like (in the global scope, so it can be used anywhere in the rest of the file), and then we’re later creating one (inside main)

If we want to use a type definition in multiple files, the type definition needs to be included in each file that needs it. To make this easy, type definitions are often placed in header files for easy propagation to all of the code files that need them:



Leave a Reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">