Search

6.x — Chapter 6 summary and quiz

Quick review

We covered a lot of material in this chapter. Good job, you’re doing great!

A compound statement or block is a group of zero or more statements that is treated by the compiler as if it were a single statement. Blocks begin with a { symbol and end with a } symbol, with the statements to be executed placed in between. Blocks can be used anywhere a single statement is allowed. No semicolon is needed at the end of a block. Blocks are often used in conjunction with if statements to execute multiple statements.

User-defined namespaces are namespaces that are defined by you for your own declarations. Namespaces provided by C++ (such as the global namespace) or by libraries (such as namespace std) are not considered user-defined namespaces.

You can access a declaration in a namespace via the scope resolution operator (::). The scope resolution operator tells the compiler that the identifier specified by the right-hand operand should be looked for in the scope of the left-hand operand. If no left-hand operand is provided, the global namespace is assumed.

Local variables are variables defined within a function (including function parameters). Local variables have block scope, meaning they are in-scope from their point of definition to the end of the block they are defined within. Local variables have automatic storage duration, meaning they are created at the point of definition and destroyed at the end of the block they are defined in.

A name declared in a nested block can shadow or name hide an identically named variable in an outer block. This should be avoided.

Global variables are variables defined outside of a function. Global variables have file scope, which means they are visible from the point of declaration until the end of the file in which they are declared. Global variables have static duration, which means they are created when the program starts, and destroyed when it ends. Avoid dynamic initialization of static variables whenever possible.

An identifier’s linkage determines whether other declarations of that name refer to the same object or not. Local variables have no linkage. Identifiers with internal linkage can be seen and used within a single file, but it is not accessible from other files. Identifiers with external linkage can be seen and used both from the file in which it is defined, and from other code files (via a forward declaration).

Avoid non-const global variables whenever possible. Const globals are generally seen as acceptable. Use inline variables for global constants if your compiler is C++17 capable.

Local variables can be given static duration via the static keyword.

Using statements (including using declarations and using directives) can be used to avoid having to qualify identifiers with an explicit namespace. These should generally be avoided.

Typedefs and Type aliases allow the programmer to create an alias for a data type. These aliases work identically to the aliased type. They can add legibility and reduce maintenance to code.

The auto keyword has a number of uses. First, auto can be used to do type inference / type deduction, which will infer a variable’s type from its initializer. Auto can also be used as a function return type to have the compiler infer the function’s return type from the function’s return statements, though this should be avoided for normal functions. Auto is used as part of the trailing return syntax. And finally, as of C++20, auto provides a shortcut for creating function templates. For now, you’ll generally use it for type deduction purposes.

Implicit type conversion is performed whenever one data type is expected, but a different data type is supplied. If the compiler can figure out how to do the conversion between the two types, it will. If it doesn’t know how, then it will fail with a compile error. Numeric promotion (or widening) occurs when a value from one fundamental data type is converted to a value of a larger fundamental data type from the same family (e.g. a short to an int). Numeric conversion occurs when we convert a value from a larger to a smaller type (e.g. int to short), or between different type families (int to double). Conversions that could cause loss of data are called narrowing conversions.

Explicit type conversion is performed when the programmer explicitly requests conversion via a cast. A cast represents a request by the programmer to do an explicit type conversion. C++ supports 5 types of casts: C-style casts, static casts, const casts, dynamic casts, and reinterpret casts. Generally you should avoid C-style casts, const casts, and reinterpret casts. static_cast is used to convert a value from one type to a value of another type, and is by far the most used-cast in C++.

Finally, C++ supports unnamed namespaces, which implicitly treat all contents of the namespace as if it had internal linkage. C++ also supports inline namespaces, which provide some primitive versioning capabilities for namespaces.

Quiz time

Question #1

Fix the following program:

Show Solution

Question #2

Write a file named constants.h that makes the following program run. If your compiler is C++17 capable, use inline constexpr variables. Otherwise, use normal constexpr variables. You can pick any value you like for max_class_size.

main.cpp:

Show Solution

Question #3

Complete the following program by writing the passOrFail() function, which should return true for the first 3 calls, and false thereafter.

Show Hint

The program should produce the following output:

User #1: Pass
User #2: Pass
User #3: Pass
User #4: Fail
User #5: Fail

Show Solution


S.4.4b -- An introduction to std::string
Index
6.17 -- Unnamed and inline namespaces

295 comments to 6.x — Chapter 6 summary and quiz

  • Wolfma

    In the constant.h file example there is a code-tag. Maybe that can't be avoided. Just a heads up.

  • KrisF

    Hi,
    I did try to code it on my own, however I ran into this error:

    Error (active)    E0349    no operator "<<" matches these operands    Quiz.S.4.x    C:\Users\kris\Documents\Visual Studio 2017\Projects\Learn_cpp\Quiz.S.4.x\Quiz.S.4.x\Quiz.S.4.x.cpp    50    

    Error    C2679    binary '<<': no operator found which takes a right-hand operand of type 'std::string' (or there is no acceptable conversion)

    Got the same error when I just copied your code.
    The error message points to this line:

    [code]     std::cout << "This " << getMonsterTypeString(monster) <<
            " is named " << monster.name <<
            " and has " << monster.health << " health.\n";[code] in void printMonster(Monster monster) function.
    What gives? Wrong settings in Options? If this is the case, I have no idea what to look for. Please help.
    Thanks.

    • nascardriver

      The code in the solution is correct. The error message sounds like your installation is corrupted. Does this code compile for you?

      • kris

        Hi nascardriver; no, it doesn't. Here are the error messages I got:

        Error    C2039    'string': is not a member of 'std'    Quiz Troubleshooting    c:\users\kris\documents\visual studio 2017\projects\learn_cpp\quiz troubleshooting\quiz troubleshooting\quiz troubleshooting.cpp    11    
        Error    C2065    'string': undeclared identifier    Quiz Troubleshooting    c:\users\kris\documents\visual studio 2017\projects\learn_cpp\quiz troubleshooting\quiz troubleshooting\quiz troubleshooting.cpp    11    
        Error    C2146    syntax error: missing ';' before identifier 'str'    Quiz Troubleshooting    c:\users\kris\documents\visual studio 2017\projects\learn_cpp\quiz troubleshooting\quiz troubleshooting\quiz troubleshooting.cpp    11    
        Error    C2065    'str': undeclared identifier    Quiz Troubleshooting    c:\users\kris\documents\visual studio 2017\projects\learn_cpp\quiz troubleshooting\quiz troubleshooting\quiz troubleshooting.cpp    11    
        Error    C2039    'cout': is not a member of 'std'    Quiz Troubleshooting    c:\users\kris\documents\visual studio 2017\projects\learn_cpp\quiz troubleshooting\quiz troubleshooting\quiz troubleshooting.cpp    12    
        Thank you.

  • Jude

    Having just done arrays. I revisited this to see if I can improve this. I feel like enum class is useless in this. Any improvements that I can make?

    • nascardriver

      The enum was supposed to be used to localize the type name and `MonsterSet::name` is "Torg", "Blurp", etc.
      Have a look at the quiz's solution.
      Strings can be initialized with empty curly braces (creating an empty string).

      • Jude

        Looked at the quiz solution and changed it, added what the quiz originally asked.

        Would it be more 'safe' if I add a "UNKNOWN" to the enum list along with the other monsters incase a bizarre error may occur.

        • nascardriver

          "unknown" or "invalid" enum entries are common, they're usually the first enumerator of the enum, so value-initialized variables of the enum type are "invalid" by default.

          This won't change anything in your code. You can remove line 40, as it's unreachable. Line 38 catches everything that isn't checked in the `switch`.

  • Tushar

    I did a bit differently. It shows me this error

    Compilation failed due to the following error(s).
    main.cpp: In function ‘int main()’:
    main.cpp:18:40: error: invalid conversion from ‘const char*’ to ‘int’ [-fpermissive]
         monsters ogre ={{"Torg",145},"ogre"};
                                            ^

    • nascardriver

      `monsters` has 3 members, but the initializer list

      has only 2 elements (One if which is another initializer list). `{ "Torg", 145 }` is used to initialize `name_of_monster`, then an attempt is made to initialize `health_to_die` with "ogre", but that doesn't work.
      You need to dissolve the inner initializer list.

      • Tushar

        I have used a nested structure following this code in the tutorial. But i can't understand why it didnt work?

        • nascardriver

          Each value in the initializers list corresponds to a value in the struct.

          The inner initializer list is used to initialize the object `CEO`. In your code, you don't have a sub-object like that.

          When you remove the inner initializer list, the members match up.

          • Tushar

            • nascardriver

              Because it's in the place where `name_of_monster` is. The first value in the initializer list is what will be used to initialize `name_of_monster`.

              An `std::string` can be created with a string and an int, the int specifies how many characters to copy.

              That's what's happening in your code with `{ "Torg", 145 }`.

  • Gacrux

    My code was 99% spot on, but instead

    I made

    which causes the following error

    main.cpp||In function 'void printMonster(MonsterData)':|
    main.cpp|32|error: could not convert 'monster.MonsterData::type' from 'monsterType' to 'MonsterData'|

    Why we need to send to printMonster the whole struct instead of just one of the elements (monster.type) like the others monster.name and monster.health?

  • derek

    I was testing my code and it seems like using "enum class" breaks the program whereas "enum" works fine.

  • Avijit Pandey

    Here is a modified version of the solution to the question.
    I modified the program so that we can take the input from the user for all the three parameters and it works.
    Now my question is, is there a way to create a single function for taking in the input for all the three parameters?

    • - Don't use `using namespace`.
      - Line 23+, 64+ Should be `else if`.
      - Line 21+: You're mapping the values to enumerators with the same values. Use a `static_cast` instead.
      - Line 87, 88: Execution order of the arguments is undefined. If you don't care, that's fine. If you want the functions to be called in a certain order, call them before you construct a monster.

      > is there a way to create a single function for taking in the input for all the three parameters?
      You're pretty close already.

      • Avijit Pandey

        Thanks for the quick response, and the tips.
        I don't understand how the execution order of the arguments is undefined in line 87 and 88.
        When I do something like

        the values of the parameters of the struct get assigned in that order.
        So, don't the three functions(used as arguments here) get called in that particular order?

        • > When I do something like [...]
          Yes, the members are initialized in that order.

          > So, don't the three functions(used as arguments here) get called in that particular order?
          No. They're evaluated in an arbitrary order and their return values are stored in temporaries. Those temporaries are then passed to the constructor. The members are still initialized
          type -> name -> health
          But the function call order is undefined.

      • Mike

        What do you mean by this? I'm doing something similar with my code, but I have no idea what you mean with this statement:

        - Line 21+: You're mapping the values to enumerators with the same values. Use a `static_cast` instead.

        It's working as is, so I'm not sure what "values" you're referring to that needs to be converted with a static-cast or to what type. Is "mapping" the same as "returning" in this context? And if so, I don't understand what other type they should be returned as other than their inherit type as currently defined.

        • Mapping means turning the values of set A into values of set B. Then, when you want to know which value in B a value from A corresponds to, you just look at the map.
          In this case, this is the map

          Recall that enumerators have integral values. They are numbered in the order of their declaration, starting at 0.

          You're using a switch-statement to turn the numbers into `Type`s. But that's not necessary, because the numbers you're using as an input are exactly the same as the enumerator's values. The entire switch-statement can be replaced with

          • Mike

            Thank you for the reply, it was comprehensive and extremely helpful to me. Without the extra details you provided, I would still be scratching my head as I don't think we've covered mapping or the idea of it in programming.

            I do have a follow up question regarding the if statement you provided. I noticed yours was missing the 2nd left parenthesis after the &&, but even when I added it, it still said I was missing a parenthesis or that one was expected. To finally get it to work, I had to add four right-sided parentheses to the end of the if statement as shown below. That doesn't seem right for this statement. If it is, please explain why, because I see as this:

            Each condition has one set of parentheses, plus one extra set for each expression in each cast. So each condition should have a total of 4 parentheses, totaling 8 for both. Yet it's requiring 12. What am I missing?

  • Sam

    Here is my solution! I appreciate any feedback, so please feel free to leave any :)

    • Man you were busy.
      Return, initialization, naming and indention from my previous comments apply here too.

      - Limit your lines to 80 characters in length for better readability.
      - `getMonsterType` and `getWeaponType` should return an `std::string_view`. This isn't covered on learncpp. Returning an `std::string` will construct a new string on every call, that's slow.

      • Sam

        Once again, thank you so much for your feedback! I updated the code, but the online compiler I am using doesn't appear to be compatible with "std::string_view". I also attempted to use it by including the <string> header, but that didn't seem to work as well. Anyways, here's the code!

        • `std::string_view` is declared in <string_view> and was added in C++17.
          Line 71, 75, 79, 83 should be indented, because they're an extension of the previous line respectively.
          You're starting to use abbreviations ("m", "w"). They're still readable, because their use is close to their declaration, but don't let this get out of hand. Time saved writing is time wasted reading.

  • Alexander S.

    Trying to make a loot box open simulation thing but when I set the amount and if the object will be given it doesn't recognize that it was changed when I run it outside the function determineLootType()

    • - Initialize your variables with brace initializers.
      - Use std::rand instead of its global-namespaced counterpart.
      - If your program prints anything, the last thing it prints should be a line feed ('\n').
      - You're using the same name style for variables, functions, and types. This will lead to confusion.
      - `main`: Missing return-statement.

      Whenever you encounter a problem, please also post code that produces the problem, the output, and the expected output.
      I'm guessing you mean this

      By default, variables are copied when you pass them to functions. `determineLootType` modifies a copy, which doesn't affect the original. You can use a reference to pass the original object

      References are covered later.

      • Alexander S.

        I never can seem to remember return 0 in main lmao. A lot of the console printing was testing stuff I was using to see if I could get the right values and stuff that I didnt remove before sharing.

        I will try to do better with naming. Also sorry for not being clear on the code.

        I have done a bit with refrences, used them in the past as I came to a sudden revaluation that they would fix the error in that program. Haven't touched them since then though so I kinds forgot they allow you to do things like that.

        Thank you for your help!

  • Alexander S.

    Any errors, I assume some might be with the Struct.

    • Hi!

      - Line 3: `using std::string;`.
      - You don't need to break after returning. A return stops everything.

      • Alexander S.

        Good to know, contemplated using std::string but I just did the using string one as a way to kind of reinforce the format of the typedef stuff.

        The return one was also one I thought about but I did it anyways just in case it breaks my switch statement.

        Thanks!

  • learning

    Improvement suggestions?

    Thanks in advance.

  • p1n

    What about this solution,instead of writing 2 functions?

  • I did my best to structure this carefully and follow best practices. I don't like jumping ahead of the lessons, but I really wanted a random number here. It seemed appropriate to nest my enum class inside of my struct, but I see that the example didn't do this. Monster::Type::Foo is getting pretty lengthy, would you recommend using typedef here? Appreciate you taking a look at this!

    EDIT: Presently Ogre is the default option, in-case the user makes an invalid choice.

    • - Use std::rand instead of its global-namespaced counterpart.
      - Use std::srand instead of its global-namespaced counterpart.
      - Use std::time instead of its global-namespaced counterpart.
      - Don't use `std::system`, it won't work on other platforms.
      - `std::time` wants a pointer, pass `nullptr`.
      - `std::srand` wants an unsigned int, add a cast. You should've gotten a compiler warning.
      - Line 59+: Should be `else if`.
      - Don't mix '\n' and `std::endl` unless you have a reason to do so.
      - Inconsistent formatting. Use your editor's auto-formatting feature.
      - Use `<ctime>` instead of `<time.h>`. Most of the `*.h` headers are C compatibility headers. The `<c*>` headers declare their contents the `std` namespace.
      - Inconsistent variable name style ("Health" vs "name"). Pick a convention, stick to it.

      > nest my enum class inside of my struct
      If you do that, you can use a regular `enum`. Name collisions are mostly prevented by the encapsulating struct. If you use a regular `enum`, you can access the enumerators via
      `Monster::DRAGON`.

      • Hey, thanks! I'm posting the updated code below. I didn't know there was an auto-formatting feature, that's very helpful. I believe that I implemented every correction except for one: I kept the std::system("CLS"); because I wanted to ask you whether or not there's an alternative. If there isn't, I will happily remove it. Also one more question, is using 'else if' as opposed to 'if' simply a matter of adhering to best practice formatting conventions, or is there something happening there?

        Appreciate your hard work very much, thanks again!

        • - Line 80: Use std::time instead of its global-namespaced counterpart.
          - Line 74: Don't mix '\n' and `std::endl` unless you have a reason to do so.
          - Line 31+: Should be `else if`.

          > I kept the std::system("CLS"); because I wanted to ask you whether or not there's an alternative
          There is no universal way of clearing the terminal/console. The terminal/console is a program in itself, how it's controlled is up to the developer of the terminal/console.

          > is using 'else if' as opposed to 'if' simply a matter of adhering to best practice formatting conventions, or is there something happening there?
          In this case, there's no difference, because each if's body returns. In general, if one condition excludes the next, use `else if`.

  • Richard Flores

    Instead of
    > if (monster.type == MonsterType::OGRE)
    Can we use
    > if (monster.type == 0)
    Since enum values evaluate to integer anyway?

    If yes, during our declaration of struct Monster, can we technically do
    > int type
    instead of
    > MonsterType type?

    • > Can we use
      If `MonsterType` is `enum class`, no. If it's a regular `enum`, yes.

      > during our declaration of struct Monster, can we technically do
      Yes.

      If you do so, you'll have no idea which number is supposed to mean what. Enums are there to prevent exactly this.

  • My program keep exit with code 2, even when I use the exact code from solution, how do I fix this?
    [code]
    #include <iostream>
    #include <string>

    // Quiz
    // Define our different monster types as enum

    enum class MonsterType {
        OGRE,
        DRAGON,
        ORC,
        GIANT_SPIDER,
        SLIME
    };

    // Our monster struct represents a single monster

    struct Monster {
        MonsterType type;
        std::string name;
        int health;
    };

    Monster monster0(MonsterType tr) {

    }
    // Return the name of the monster's type as a string
    // Since this could be used elsewhere, it's better to make this its own function

    std::string getMonsterTypestring(Monster monster) {
        if (monster.type == MonsterType::DRAGON) {
            return std::string("Dragon");
        }
        if (monster.type == MonsterType::GIANT_SPIDER) {
            return std::string("Giant Spider");
        }
        if (monster.type == MonsterType::OGRE) {
            return std::string("Ogre");
        }
        if (monster.type == MonsterType::ORC) {
            return std::string("Orc");
        }
        if (monster.type == MonsterType::SLIME) {
            return std::string("Slime");
        }

        // In case we entered something else
        return std::string("???");
    }

    void printMonster(Monster monster_real) {
        std::cout << "This " << getMonsterTypestring(monster_real) << " is named " << monster_real.name << " and has " << monster_real.health << "health\n";
    }

    int main() {

        Monster ogre = { MonsterType::OGRE, "Torg", 145 };
        Monster slime = { MonsterType::SLIME,"Blurp", 23 };

        printMonster(ogre);
        printMonster(slime);

        return 0;
    }
    [code]

  • Jonathan

    The longest code I"ve ever wrote! Please give me some comment!

    • Jonathan

      I've updated a bit.

      • * Initialize your variables with brace initializers. If you add a member to @monsterData and forget to assign to it in @getMonsterInfo, this member will have an undefined value. Either initialize @monster, or all members in the struct definition.
        * You're using the same name style for variable, functions, and types. This will lead to confusion.
        * Avoid abbreviations. The time you save writing them is time wasted reading them. Code is more often read than written, you're wasting time.
        * Don't pass 32767 to @std::cin.ignore. Pass @std::numeric_limits<std::streamsize>::max().
        * @getTBDA: You don't need temporaries, you can return directly

        * @traitString can always be determined by @trait. Remove the member and add a conversion function, just like you did with @getTypeString.
        * Inconsistent formatting. Use your editors auto-format feature.
        * @getTypeString::num should be an @monsterType.
        * Line 116, 123: Don't use string concatenation to print, use the stream's @operator<<.

        • Jonathan

          thanks for the detailed suggestions! For the function of getting string, is it better to use switch() instead of using if/else statement? I just go through the control flow!

          And for the abbreviation, I feel like "TBDA" seems to be more readable than "TraitBloodDefenceAttack".

          • > is it better to use switch() instead of using if/else statement?
            Whenever you have a limited set of values (eg. char, enum), use a switch. The compiler can optimize switch-statements better than if-statements.

            > I feel like "TBDA" seems to be more readable than "TraitBloodDefenceAttack"
            You might understand it, but everyone else who reads your code has no idea what TBDA is supposed to mean. And you won't either, if you look back at your code in a couple of months or years (You might not look back at this code, but some projects will require maintenance after a long time).

  • Merlin

    Is this a problem if I don't define an enum class? There is a shorter way and it works fine!
    (like the solution)

    I would really like to hear your thoughts about it, thanks! :)

  • Dimbo1911

    Any advices or suggestions on the following?

    • Hi!

      * Line 14, 15, 16: Initialize your variables with brace initializers.
      * @main: Missing return-statement.
      * Line 23-29 should use "else if". (Covered next chapter)
      * Line 39: Unnecessary space before line break.

      What's up with the Xs in front of every comment? It makes them harder to read.

      • Dimbo1911

        Thank you for your input, is there any difference in "if" and "else if", if every of the cases will result in return statements?
        Since they are not real comments but more of a to do list (I usually do not keep them in code, or move them to a different .txt file), I keep track of stuff I have doen with putting X in front of it haha

        • > is there any difference in "if" and "else if", if every of the cases will result in return statements?
          No. If conditions exclude each other, they should be in else-ifs. That way, if you decide to remove the return or replace it with something else, your code will still work as expected. It also allows a reader to understand your code without having to look at the if's body.

  • Mitchell Zakocs

    Just finished the quiz question, would love any criticism you can provide.

    • Hi Mitchell!

      * Line 12, 13, 14: Initialize your variables with brace initializers.
      * Line 32: Limit your lines to 80 characters in length for better readability on small displays.
      * Line 20ff: Should be else-if.

  • Alireza

    Hi there,

    I've done that quiz. I think I've just used another way to do that, however.

    Is this code good to use the same ?

    • * Line 15, 16, 65: Initialize your variables with brace initializers. You used copy initialization.
      * Line 18, 21: Initialize your variables with brace initializers. You used direct initialization.
      * Line 15: Use @std::string's default constructor (ie. don't pass arguments).
      * Line 30-50: Should be else if.
      * @printMonsterStat doesn't need a forward declaration if it's declared above @setMonsterStat.

      They're called ogre.

  • Brian Gaucher

    I completed it solving all my issues alone, without peeking.
    But could I improve anything. (Looking for feedback)

    I also typed it all in Dvorak Programmer layout.

    • Hi Brian!

      * Line 13, 14, 15: Initialize your variables with uniform initialization.
      * @printType, @printMonster: Bad name
      * @printType: If you have a limited set of values, use a switch. It allows for better compile-time optimization.
      * @printType: Return a const char *. Constructing an @std::string every time @printType is called is slow.
      * @main: Missing return value

      > I also typed it all in Dvorak Programmer layout
      Let me know how it went once you got used to it. I've never learned it, because I'm afraid I'll forget qwerty and be lost when I have to use a regular layout. Switching from qwertz (ger) to qwerty (us) was a good step for me.

  • Rai

    I decided to make a small RPG. I'm having some problems so if anyone could help me out here.

    main.cpp

    attack.cpp

    attack.h