Project 4 Frequently Asked Questions

Updated 11/4/2019

The most recent items appear first.


I'm developing in MSVS and I can't get the View axis labeling to match the samples, even though I'm using what the Stream Formatting Handout says to set the cout stream precision. In fact, in the View sample, in some cases the axis labels don't seem to follow a consistent pattern. What's going on?

First, reread the discussion about the approximate nature of floating point calculations in the project document, and how tiny differences can flip a result into one bin or another when the results are "quantized" - which is exactly what the axis labels involve. 


So in general, when you calculate the label value for each third row or column of the grid, and this value gets rounded to the nearest integer thanks to setting the stream precision to 0, it won't necessarily follow a simple pattern.  


That said, for some odd reason MSVS's Standard Library doesn't produce the same rounded value as gcc's Standard Library, or the LLVM Standard Library in Xcode. Since gcc 9.1.0 is our reference environment, you need to write your code to produce the correct results on CAEN using gcc 9.1.0. The simplest way to do this is to finalize matching your code output with samples of the axis labels on CAEN, and not be concerned about the axis discrepancies in MSVS. 


A more complicated, but instructive, way is to write two versions of the axis label code, one using the specified stream formatting, and the other with whatever works and matches the samples in MSVS (e.g. using <cmath>'s round() function). Then use "conditional compilation" preprocessor commands to select the code version depending on which platform you are compiling on. See how this code would look on the web page http://www.umich.edu/~eecs381/progenvs/C++11Status.txt


What are some handy C++17 tricks that I can use to make my code simpler and clearer? 

Here are some -- you can use these or not, at your option, but they are convenient and worth knowing.


1. You can instantiate a class template with its constructor parameters:

Since std::pair<> has a constructor that is a function template, you can create an unnamed pair object with:

std::pair (int_var, string_var); 

instead of either:

std::pair<int, string> (int_var, string_var)

or

std::make_pair(int_var, string_var)


For example, if my_map is std::map<int, string>, you can do this:

my_map.insert(pair(int_var, string_var));

or

my_map.emplace(int_var, string_var);


To take advantage of this feature, the constructor has to have all of the template parameter types, as in std::pair<>.


2. The if statement now supports both an initialization and a test in the condition. 

This syntax is based on the first two parts of a for statement: 

for(initialization; test; increment) {   . . . } 

where the initialized variable is scoped throughout the for loop body.  Likewise, you can have an if statement of the parallel form:

if(initialization; test) {

. . .

}

else {

. . .

}

where the initialized variable is scoped throughout both the if-body and the else-body. 


A simple example: Instead of writing:

Thing* get_thing_ptr(const string& name)

{

auto iter = thing_ptrs.find(name);

if(iter == thing_ptrs.end()) {

throw Error("Thing not found!");

}

return iter->second;

}


You can write:

Thing* get_thing_ptr(const string& name)

{

    if(auto iter = thing_ptrs.find(name); iter == thing_ptrs.end()) {

        throw Error("Thing not found!");

        }

    else {

        return iter->second;

        }

}



3. "Structured bindings" allow you to assign alias names to the members of returned std::pair.

Instead of writing the following clumsy and less-than-mnemonic code:


std::pair<my_set_iterator_type_t, bool> result = my_set.insert(datum);

or

auto result = my_set.insert(datum)


if(result.second) {

cout << "successful insertion" << endl;

}

else {

cout << "insertion failed" << endl;

cout << *(result.first) << endl; // output the item that was already in the container

}


You can write (using some wacky syntax):

auto [iter, success] = my_set.insert(datum);

// iter is an alias for the first of the returned pair; success is an alias for the second, and both have the actual types

if(success) {

cout << "successful insertion" << endl;

}

else {

cout << "insertion failed" << endl;

cout << *iter << endl; // output the item that was already in the container

}


This works for not just returned std::pair, but also for std::tuple and other aggregate types. 


If you want a more complete tutorial on these features, with additional examples, see

https://www.fluentcpp.com/2018/06/19/3-simple-c17-features-that-will-make-your-code-simpler/