Skip to the content of the web site.

Using artifical intelligence

IEEE Spectrum Magazine    MIT Technology Review    Wired Magazine    EDN Network    EE Times    Skeptic's Guide to the Universe

Artificial intelligence does have the opportunity to help you understand issues with your programs, but it should not replace learning how to program. Programming is nothing more than rigorous thinking when problem solving: you can explain to a friend how to perform a calculation, but your friend can also interpret what you said, and sometimes compensate for errors that you make, or deal correctly with ambiguities. Your friend, however, is no faster than any other human, so if you want to perform calculations quickly, you must instead describe to a computer how to perform that calculation. The only problem is that the programming languages are designed to avoid ambiguities, the compiler will not correct for mistakes you made, and if you don't know what you really want to do, then the computer won't help you, either.

As an engineer, you will be designing solutions to real-world problems, and no one will pay you to design solutions that have already been developed. You will have to problem solve, and while artificial intelligence may be able to aid you, it is unlikely to solve the problem. The tools you will need to develop to solve problems start here: solving problems that have already been solved, and to then proceed to hone these problem-solving tools to newer problems. In your fourth year, you well engage in a year-long fourth-year design project where you will actually solve a real-world problem. If you don't learn problem-solving skills right now, you will not do any better than any other of your high-school peers who did not go into engineering. Also, you may not pass the mid-term or final examinations, which do not give you access to artificial intelligence tools.

For example, one graduate from the program entered a very specific field of engineering related to insurance, including the design of climate models and developing plans for robust yet economic strategies mitigating the effects of climate change. When the developers at this company engaged ChatGPT and other AI tools, they found that what was produced was wrong or useless. These tools can only produce results that are based on solutions that they have already been exposed to. Thus, if you do not build up your skill sets at solving simpler problems, you will not be able to apply your knowledge to solve novel problems. If all you can solve is what has already been solved, why would anyone pay you to do anything? You must hone your skills as a developer and as an engineer by working on solved problems, so that you build up your skill set to solve new and unsolved problems.

Please remember, that when you have a problem that you have to solve, be it in programming, in calculus or in circuit design, if you just look at the solution, the correctness of the answer is obvious, and thus the solution is "of course, correct" (after all, what else could it be). Unfortunately, this does not help you develop your problem-solving skills: when you sit down to do the mid-term or the final examination, you will not be able to just come up a solution out-of-the-blue if your problem-solving skills are not built up. You will have the idea that you can problem solve, but the examinations will quickly dissuade you of that illusion.

Additionally, many companies are taking steps (countermeasures) to ensure that their designs and solutions do not get harvested by artificial intelligence systems, so you will not be able to leverage such tools in developing solutions in domain specific situations. Consequently, if you do not develop your problem-solving while studying in your undergraduate program, it will be much more difficult to find solutions to novel problems once you begin working as an engineer.

As relayed by an electrical engineering student from this student's senior courses, ChatGPT is a useful tool for generating initial code snippets and designs in software development and register-transistor logic (RTL), but it has notable limitations. While it can help identify mistakes and generate new code, it is not always accurate and struggles with complex algorithms and optimized designs. Errors in generated code can lead to costly and critical failures, especially in hardware design where simulation success does not guarantee synthesis success. Ultimately, engineers are responsible for catching mistakes and ensuring the reliability of their work, as relying solely on ChatGPT without a deep understanding of the subject can lead to serious consequences. A deep understanding of the subject, however, can only be achieved by struggling to find solutions to easier and known problems; by using artificial intelligence to provide solutions, you fail to gain the necessary skill sets to solve more difficult real-world problems.

Suggestions:

  • Never use an AI to begin solving your problem. If you are using an IDE with an artificial intelligence assistant, turn it off.
  • If your code does not compile, try to find the issue first, but if you cannot find the issue, ask ChatGPT: The following code does not compile. Can you give me a hint as to what is wrong? Don't tell me what is wrong, just point to the line.
  • If the hint does not help, then ask it directly: The following code does not compile. Can you explain what is wrong with the code? However, be sure to read the explanation as to what is wrong first before you just cut-and-paste the solution.
  • If your code compiles, but does not does do what you expect it to do, again, ask it first for hints: This code should implement the selection sort algorithm, but it doesn't seem to work. Please, do not tell me what is wrong, but rather can you tell me where I should look to find the problem, or give me some sample code that demonstrates the error?

Here are some examples of ChatGPT in action:

  1. a logic error
  2. a syntax error
  3. sub-optimal code
  4. a linked-list class
  5. merging two arrays

Example 1

Here, the code compiles, but returns the wrong answer:

Why does the following code not correctly calculate
the sum of the integers from 1 to n?

#include 

int main();

int main() {
    int n{};
    std::cout << "Enter an integer: ";
    std::cin  >> n;

    int sum{ 0 };

    for ( int k{ 0 }; k < n; ++k ) {
        sum += k;
    }

    std::cout << "The sum of the integers from 1 to 'n' is "
                << sum << std::endl;

    return 0;
}

The code you provided has a small logical error in the loop condition. It should be `k <= n` instead of `k < n` in order to correctly calculate the sum of integers from 1 to n.

Here's the corrected version of the code:

In the original code, the loop runs from `k = 0` to `k < n`, which includes all integers from 0 to n-1. By changing the loop condition to `k <= n`, you ensure that the loop iterates over integers from 1 to n, inclusive, resulting in the correct sum calculation.

Example 2

If you have a functioning program, but there is a syntax error or two, but for whatever reason, you cannot deduce where the errors are, you can also use ChatGPT:

Why does this code not compile?

#include > n;

    int sum{ 0 ;

    for (int k{ 1 }; k <= n; ++k {
        sum += k;
    }

    std::cout << "The sum of the integers from 1 to "n" is "
              << sum << std:endl;
    return 0;
}

The code you provided has a few syntax errors that prevent it from compiling correctly. I'll go through them and provide you with the corrected version:

  1. Added a missing `>` in the `#include <iostream>` line.
  2. Corrected the initialization of the `sum` variable by adding a missing closing brace (``}).
  3. Corrected the for loop syntax by adding a missing closing brace (`}`) and a missing closing parenthesis after `++k`.
  4. Replaced the incorrect double quotes (`"`) around `n` with the correct double less-than and greater-than symbols (`<<` and `>>`) for proper stream insertion/extraction.
  5. Corrected the output line by using `<<` instead of `:`.
  6. Replaced `std:endl` with `std::endl` for proper namespace access.

With these corrections, the code should compile and run as expected.

The comments in the code include

// You missed a closing brace here
// You missed a closing brace here and a closing parenthesis after ++k
// Used << instead of ""

Now, to be fair, ChatGPT made an incorrect assumption of what I wanted. In some programming languages, having something like

"The sum of " n " and " m " is " (m + n)

automatically concatenates the integer values of m, n and m + n to the string. Instead, what I wanted was to escape the double quotes:

       "The sum of the integers from 1 to \"n\" is "

However, the comment is still valid, as is the correction. Actually, it's probably nicer, as it hides the variable name.

Example 3

Here is another example: I found some awkward code on the web to calculate the greatest common divisor of two integers, and I asked ChatGPT about them:

Does this correctly calculate the gcd of two numbers?

#include 
using namespace std;

int main() {
  int n1, n2, hcf;
  cout << "Enter two numbers: ";
  cin >> n1 >> n2;

  // swapping variables n1 and n2 if n2 is greater than n1.
  if ( n2 > n1) {
    int temp = n2;
    n2 = n1;
    n1 = temp;
  }

  for (int i = 1; i <=  n2; ++i) {
    if (n1 % i == 0 && n2 % i ==0) {
      hcf = i;
    }
  }

  cout << "HCF = " << hcf;

  return 0;
}
The code you've provided attempts to calculate the greatest common divisor (gcd) of two numbers using a loop and the basic definition of gcd. While it will give you the correct result, there's an optimization that can be made to improve the efficiency of the algorithm. The optimization involves using the Euclidean algorithm, which is a more efficient way to calculate the gcd of two numbers. Here's the optimized version of your code using the Euclidean algorithm:

In this version, the `gcd` function calculates the gcd of two numbers using recursion and the Euclidean algorithm. It's more efficient than the previous approach as it reduces the larger number quickly and iteratively, leading to fewer iterations overall.

Does this code correctly calculate the gcd of two numbers?

#include 
using namespace std;

int main() {
  int n1, n2;

  cout << "Enter two numbers: ";
  cin >> n1 >> n2;

  while(n1 != n2) {
    if(n1 > n2)
      n1 -= n2;
    else
      n2 -= n1;
  }

  cout << "HCF = " << n1;

  return 0;
}

Yes, the code you've provided correctly calculates the greatest common divisor (gcd) of two numbers using the Euclidean algorithm, which involves iteratively subtracting the smaller number from the larger one until both numbers become equal. The final value of either `n1` or `n2` will be the gcd of the original numbers.

However, please note that this method is not the most efficient way to compute the gcd, especially for large numbers. The method I provided in my previous response, using the recursive Euclidean algorithm, is more efficient and requires fewer iterations.

But if you're looking for a simple and straightforward implementation, the code you've given will indeed give you the correct gcd result.

Can you give me C++ code for a function that calculates the gcd of two numbers without using recursion?

Sure, here's an example of a C++ function that calculates the Greatest Common Divisor (GCD) of two numbers using a loop without recursion:

In this code, the gcd function uses the Euclidean algorithm to calculate the GCD of two numbers without recursion. The algorithm iteratively computes the remainder of the division until the remainder becomes zero. The final non-zero remainder is the GCD of the two numbers. The main function takes two user-input numbers, calls the gcd function, and then prints the GCD.

Copy and paste this code into a C++ compiler to test it out.

Example 4

Later in this course, you will be required to program a linked list, and you will need to be able to author such code on the final examination. The code I wrote below was written in haste, without serious thought, and with bugs knowingly introduced. Fortunately, it compiled the first time, so there were no syntax errors what-so-ever, but there were many other problems. While you may not understand all of the code right now, you can read the criticisms of ChatGPT that follow, and hopefully you will appreciate that they do make sense. You will note that I do not even state in the question what the code is about.

What is everything wrong with this class?

// Class declarations
class Node;
class Buggy_linked_list;

  ///////////////////////
 // Class definitions //
///////////////////////
class Node {
  public:
    int   value_;
    Node *p_next_;
};

class Buggy_linked_list {
  public:
    Buggy_linked_list();
   ~Buggy_linked_list();
    Buggy_linked_list( Buggy_linked_list const &orig );
    Buggy_linked_list( Buggy_linked_list      &&orig );

    Buggy_linked_list &operator=( Buggy_linked_list const &rhs );
    Buggy_linked_list &operator=( Buggy_linked_list      &&rhs );

    void push_front( int n );
    int front() const;
    void pop_front();

  private:
    Node *p_head_;
};

Buggy_linked_list::Buggy_linked_list() {
  // Empty constructor
}

Buggy_linked_list::~Buggy_linked_list() {
  p_head_ = nullptr;
}

Buggy_linked_list::Buggy_linked_list( Buggy_linked_list const &orig ) {
  for ( Node *p_node{ orig.p_head_ };
        p_node != nullptr;
        p_node = p_node->p_next_ ) {
    push_front( p_node->value_ );
  }
}

Buggy_linked_list::Buggy_linked_list( Buggy_linked_list &&orig ) {
  p_head_ = orig.p_head_;
  orig.p_head_ = nullptr;
}

Buggy_linked_list &Buggy_linked_list::operator=( Buggy_linked_list const &rhs ) {
  p_head_ = nullptr;

  for ( Node *p_node{ rhs.p_head_ };
        p_node != nullptr;
        p_node = p_node->p_next_ ) {
    push_front( p_node->value_ );
  }
}

Buggy_linked_list &Buggy_linked_list::operator=( Buggy_linked_list &&rhs ) {
  p_head_ = rhs.p_head_;
  rhs.p_head_ = nullptr;
}


void Buggy_linked_list::push_front( int n ) {
  p_head_ = new Node{ n, p_head_ };
}

int Buggy_linked_list::front() const {
  p_head_->value_;
}

void Buggy_linked_list::pop_front() {
  delete p_head_;
  p_head_ = p_head_->p_next_;
}

There are several issues with the provided class and its implementations. I'll point out the problems and suggest corrections:

Node Definition:

The Node class definition is missing the constructor. You need to provide a constructor to initialize the `value_` and `p_next_` members.

Buggy_linked_list::front():

The front() function in the Buggy_linked_list class is missing a return statement. It should return the value of the front node.

Buggy_linked_list::operator= (Copy Assignment Operator):

The copy assignment operator is missing a return statement. It should return the instance (*this) after the copy is performed.

Buggy_linked_list::operator= (Move Assignment Operator):

The move assignment operator is missing a return statement. It should return the instance (*this) after the move is performed.

Buggy_linked_list::pop_front():

In the pop_front() function, you should check if p_head_ is not nullptr before attempting to access and delete it.

Destructor (Buggy_linked_list::~Buggy_linked_list()):

The destructor should properly deallocate all nodes in the linked list.

Memory Management:

The copy constructor and copy assignment operator should properly deallocate any existing nodes in the target object before performing the copy.

After making these corrections, the class and its member functions should work as expected. Always remember to manage memory correctly to avoid memory leaks and undefined behavior.

After making these corrections, we now have something more reasonable; however, you will note that we need to delete all existing nodes in the destructor, and then ChatGPT has told us to do this also in both the assignment operator and the move operator. Instead of cutting and pasting the same code three times, I instead wrote a clear() function that performs this operation, and then called that function in the three required locations. We will start a new session and see what ChatGPT thinks now (you don't have to start a new session, but I'm doing so to clear any previous immediate record of our discussion).

What is wrong with this code?

// Class declarations
class Node;
class Buggy_linked_list;

  ///////////////////////
 // Class definitions //
///////////////////////
class Node {
  public:
    Node( int value, Node *p_next = nullptr );
    int   value_;
    Node *p_next_;
};

class Buggy_linked_list {
  public:
    Buggy_linked_list();
   ~Buggy_linked_list();
    Buggy_linked_list( Buggy_linked_list const &orig );
    Buggy_linked_list( Buggy_linked_list      &&orig );

    Buggy_linked_list &operator=( Buggy_linked_list const &rhs );
    Buggy_linked_list &operator=( Buggy_linked_list      &&rhs );

    void push_front( int n );
    int front() const;
    void pop_front();

  private:
    void clear();
    Node *p_head_;
};

Node::Node( int value, Node *p_next ):
value_{ value },
p_next_{ p_next } {
  // Empty constructor
}

Buggy_linked_list::Buggy_linked_list() {
  // Empty constructor
}

Buggy_linked_list::~Buggy_linked_list() {
  clear();
}

Buggy_linked_list::Buggy_linked_list( Buggy_linked_list const &orig ) {
  for ( Node *p_node{ orig.p_head_ };
        p_node != nullptr;
	p_node = p_node->p_next_ ) {
    push_front( p_node->value_ );
  }
}

Buggy_linked_list::Buggy_linked_list( Buggy_linked_list &&orig ) {
  p_head_ = orig.p_head_;
  orig.p_head_ = nullptr;
}

Buggy_linked_list &Buggy_linked_list::operator=( Buggy_linked_list const &rhs ) {
  if ( this != &rhs ) {
    // Properly deallocate existing nodes if needed
    clear();

    for ( Node *p_node{ rhs.p_head_ };
          p_node != nullptr;
	  p_node = p_node->p_next_ ) {
      push_front( p_node->value_ );
    }
  }

  return *this;
}

Buggy_linked_list &Buggy_linked_list::operator=( Buggy_linked_list &&rhs ) {
  if ( this != &rhs ) {
    // Properly deallocate existing nodes if needed
    clear();

    p_head_ = rhs.p_head_;
    rhs.p_head_ = nullptr;
  }

  return *this;
}


void Buggy_linked_list::push_front( int n ) {
  p_head_ = new Node{ n, p_head_ };
}

int Buggy_linked_list::front() const {
  return p_head_->value_;
}

void Buggy_linked_list::pop_front() {
  if ( p_head_ != nullptr ) {
    Node *p_tmp{ p_head_ };
    p_head_ = p_head_->p_next_;
    delete p_tmp;
  }
}

void Buggy_linked_list::clear() {
  while ( p_head_ != nullptr ) {
    Node *p_tmp = p_head_;
    p_head_ = p_head_->p_next_;
    delete p_tmp;
  }
}

The provided code appears to be a linked list implementation with some constructors, destructor, and member functions. While the code seems mostly correct, there are a few potential issues and improvements to consider:

  1. Missing Copy Constructor: The code lacks a copy constructor, which can lead to issues when creating new instances from existing instances. You've implemented the copy assignment operator (`operator=`), but having a copy constructor (`Buggy_linked_list(const Buggy_linked_list &orig)`) is also important for consistent behavior.
  2. Deep Copy in Copy Constructor: The copy constructor is implemented using a loop that repeatedly calls `push_front()`. This results in a reversed order of elements in the new list. Additionally, it's inefficient to rebuild the list this way. It's better to create a deep copy of the linked list by iterating through the original list and creating new nodes.
  3. Memory Leaks: The destructor, copy constructor, and copy assignment operator all utilize `push_front()` to create new nodes. This approach can lead to memory leaks if there is insufficient memory to allocate new nodes. It's better to iterate through the existing nodes and delete them properly in the destructor and the `clear()` method.
  4. Ownership and Rule of Three/Five: The code is implementing some aspects of the Rule of Three (destructor, copy constructor, copy assignment operator) and Rule of Five (move constructor, move assignment operator), but it's not fully consistent. You should either fully adhere to these rules or consider using C++11's Rule of Zero, utilizing smart pointers and avoiding manual memory management.
  5. No Exception Safety: The code doesn't handle exceptions properly. For example, if memory allocation fails during `push_front()`, the linked list will be in an inconsistent state with a memory leak. Exception safety should be considered when dealing with memory allocation.
  6. Front Operation Safety: The `front()` method assumes that the list is non-empty when called. It would be better to check if the list is empty before accessing p_head_.
  7. Using `nullptr` Instead of `NULL`: You're consistently using `nullptr` for null pointers, which is the modern C++ way. However, the declaration of class `Buggy_linked_list` includes `NULL` for class forward declaration, which is inconsistent.
  8. Code Formatting: While not incorrect, consistent code formatting can improve code readability. Make sure to use consistent indentation and spacing throughout your code.

These points aside, the code does demonstrate the basic mechanics of a linked list and the usage of constructors, destructors, and assignment operators. To improve the code's correctness and efficiency, consider addressing the points mentioned above.

It seems that there are more issues than what were initially indicated. I will discuss each point, and then make appropriate changes to the source code.

  1. This is an error in ChatGPT, as Class_name( const Class_name &orig ) is what it is expecting, but Class_name( Class_name const &orig ) is also a valid declaration of the copy constructor. I'll change the declaration just to make ChatGPT happy.
  2. Cool! It realized that when I make a copy of the linked list, the integers are being added in the reverse order! I wasn't expecting ChatGPT to discover this, but nice. You will notice that copies are made twice, so rather than cut-and-paste code, I created a second helper function.
  3. I'm not sure what it says here, as neither the destructor nor clear() call push_front(...). My code, however, does assume that new{...} will return a newly allocated node, so I will check to make sure that it did.
  4. This is another issue with where const appears. I'll fix it so that ChatGPT is happy.
  5. Dealt with, already. There is a try-catch inserted.
  6. Fixed, too. I now throw an exception.
  7. I think that ChatGPT is trying to tell me that I did not initialize p_head_, but I'm not sure. Below I'll ask ChatGPT what it meant.
  8. My code is consistently formatted... I think what it is complaining about is how I add extra spaces to line up common elements in different lines in the member function declarations in the class definitions. Too bad, so sad, I'm not changing that...

Let us try a third time (actually, it required a few extra efforts, as ChatGPT sometimes did not respond appropriate or timed out or failed). Thus, we have the following.

What about the following implementation of a simple linked list in C++?

#pragma once
#include 

// Class declarations
class Node;
class Buggy_linked_list;

  ///////////////////////
 // Class definitions //
///////////////////////
class Node {
  public:
    Node( int value, Node *p_next = nullptr );
    int   value_;
    Node *p_next_;
};

class Buggy_linked_list {
  public:
    Buggy_linked_list();
   ~Buggy_linked_list();
    Buggy_linked_list( const Buggy_linked_list  &orig );
    Buggy_linked_list(       Buggy_linked_list &&orig );

    Buggy_linked_list &operator=( const Buggy_linked_list  &rhs );
    Buggy_linked_list &operator=(       Buggy_linked_list &&rhs );

    void push_front( int n );
    int front() const;
    void pop_front();

  private:
    void clear();
    void copy( const Buggy_linked_list &orig );
    Node *p_head_;
};

Node::Node( int value, Node *p_next ):
value_{ value },
p_next_{ p_next } {
  // Empty constructor
}

Buggy_linked_list::Buggy_linked_list():
p_head_{ nullptr } {
  // Empty constructor
}

Buggy_linked_list::~Buggy_linked_list() {
  clear();
}

Buggy_linked_list::Buggy_linked_list(
  const Buggy_linked_list &orig
):
p_head_{ nullptr } {
  copy( orig );
}

Buggy_linked_list::Buggy_linked_list(
  Buggy_linked_list &&orig
) {
  p_head_ = orig.p_head_;
  orig.p_head_ = nullptr;
}

Buggy_linked_list &Buggy_linked_list::operator=( const Buggy_linked_list &rhs ) {
  if ( this != &rhs ) {
    // Properly deallocate existing nodes if needed
    copy( rhs );
  }

  return *this;
}

Buggy_linked_list &Buggy_linked_list::operator=(
  Buggy_linked_list &&rhs
) {
  if ( this != &rhs ) {
    // Properly deallocate existing nodes if needed
    clear();

        p_head_ = rhs.p_head_;
    rhs.p_head_ = nullptr;
  }

  return *this;
}


void Buggy_linked_list::push_front( int n ) {
  p_head_ = new Node{ n, p_head_ };
}

int Buggy_linked_list::front() const {
  if ( p_head_ == nullptr ) {
    throw std::runtime_error{ "The linked list is empty." };
  }

  return p_head_->value_;
}

void Buggy_linked_list::pop_front() {
  if ( p_head_ != nullptr ) {
    Node *p_tmp{ p_head_ };
    p_head_ = p_head_->p_next_;
    p_tmp->p_next_ = nullptr;
    delete p_tmp;
    p_tmp = nullptr;
  }
}

void Buggy_linked_list::clear() {
  while ( p_head_ != nullptr ) {
    Node *p_tmp = p_head_;
    p_head_ = p_head_->p_next_;
    p_tmp->p_next_ = nullptr;
    delete p_tmp;
    p_tmp = nullptr;
  }
}

void Buggy_linked_list::copy( const Buggy_linked_list &orig ) {
  clear();

  if ( orig.p_head_ != nullptr ) {
    p_head_ = new Node{ orig.front(), nullptr };

    for ( Node *p_orig{ orig.p_head_->p_next_ },
               *p_copy_tail{ p_head_ };
          p_orig != nullptr;
          p_orig = p_orig->p_next_,
          p_copy_tail = p_copy_tail->p_next_ ) {
      p_copy_tail->p_next_ = new Node{ p_orig->value_, nullptr };
    }
  }
}

The provided code seems to be mostly correct and implements a basic linked list with some functionality. However, there are a few things that could be improved or corrected:

  1. Resource Management in copy Function: In the copy function, you are copying the contents of one linked list into another. If an exception occurs while allocating memory for the new nodes, the already allocated nodes won't be properly cleaned up. You should handle memory cleanup properly when an exception is thrown during memory allocation.
  2. Move Assignment Operator: In the move assignment operator, you should check if this is the same as &rhs before performing any operations. If they are the same, you don't need to do anything.
  3. Destructive Copy in Move Constructor: In the move constructor, you're simply moving the p_head_ pointer from orig to the new object. However, this will result in orig and the new object both having the same p_head_ pointer, which could lead to problems when destructors are called. It's generally better to create a new copy of the linked list in the move constructor.
  4. Error Handling in front and pop_front: In the front and pop_front functions, when the linked list is empty (p_head_ is nullptr), it's better to throw std::logic_error instead of std::runtime_error. This is because std::runtime_error is usually used for errors that can occur at runtime, while std::logic_error is more appropriate for logic-related errors.
  5. Destructor Call Order: In the destructor, you're calling the clear function, which properly deallocates the nodes. However, it's worth noting that the destructor for Node will also be called for each node as they are deallocated. If the destructor for Node is doing any additional cleanup, you should ensure that it's properly handled.
  6. Include Guards: You've used #pragma once to guard against multiple includes, but you've also included the header without guards. It's a good practice to use include guards for all headers.
  7. Copy Constructor and Copy Assignment Operator: In the copy method, the code iterates through the original linked list, copying each element to the new list. This approach is correct, but one thing to note is that the copy method doesn't first clear the existing contents of the linked list. So, if you're copying into an existing instance, you might end up with a mix of old and new nodes. It might be a good idea to clear the current linked list before copying, or consider implementing a swap operation to safely and efficiently handle copying.
  8. Initializer Lists in Constructors: You can use initializer lists in constructors to initialize member variables directly, rather than assigning values within the constructor body. This can lead to cleaner and more efficient code.
  9. Clearing Memory in Destructor: Your destructor calls the clear() method, which releases memory for each node. While this is correct, it's not necessary to manually set pointers to nullptr for each node you're deleting, as you're already deleting them immediately afterward.
  10. Consistent Naming Convention: It's good practice to use a consistent naming convention for member variables. You've used the prefix p_ for some member variables (e.g., p_head_, p_next_), but not for others (e.g., value_). Consistency can improve readability.
  11. No Default Constructor for Node: The Node class does not have a default constructor. If you ever need to create a Node without providing a value or next pointer, you would need to add a default constructor.
  12. Edge Cases: When copying or moving the linked list, make sure to handle edge cases properly, such as the case when the original linked list is empty.
  13. Comments and Documentation: Adding comments and documentation can greatly improve the readability of your code, making it easier for others (and yourself) to understand its purpose and functionality.

Here's an example of how some of these points could be addressed:

Please note that while these suggestions can improve the code's robustness and readability, they might not cover all possible use cases or optimizations you might want to implement depending on the specific requirements of your application.

Okay, you can look at the final implementation that take into account some of these comments at replit.com, and for your entertainment, here is the original at replit.com. Here are also some comments on the comments.

  1. Hmmm... Not a bad idea, but I have one better: if the copy is not successful, the linked list should be changed to what it was originally.
  2. I believe I already do this; correct me if I'm wrong.
  3. Yes, but in the very next line, I set orig.p_head_ = nullptr;.
  4. Okay, I thought runtime would be better, but it would be an error in coding, too.
  5. Its okay here, though.
  6. Okay, they want both kinds of header guards, sure, I'll add that.
  7. I'm not sure about this observation... I think it is an error.
  8. This is beyond a first-year course.
  9. Now its telling me I'm doing too much work. :-) You can't win.
  10. The p_ indicates its a "pointer to" something, so this is a silly comment.
  11. Good point.
  12. Odd, as I am using the clear() member function...
  13. True, I don't want a default constructor for the Node class.
  14. I believe I hand the edge cases correctly.
  15. There are no comments or documentation, so completely valid!

Example 5

Here, we will see how powerful ChatGPT is, but this uses features not taught in our course.

Write a C++ function that merges two primitive C++ arrays with capacities M and N, respectively.

Certainly! Here's a C++ function that merges two primitive C++ arrays with capacities M and N, where M and N are the maximum capacities of the input arrays. The merged array will have a capacity of M + N, and it will contain the elements from both input arrays:

In this code, we define a mergeArrays function that takes two primitive integer arrays (arr1 and arr2), their capacities (M and N), and a destination array (merged) with a capacity of M + N. The function iterates through both input arrays, comparing elements, and adding them to the merged array in sorted order. After the merge is complete, it populates the merged array with the merged elements.

The main function demonstrates how to use the mergeArrays function with two example arrays and prints the merged result.

First, I would strongly recommend that you not do this if you are trying to learn to implement the merging of two arrays. It won't help, because knowing the solution does not help you build up your personal set of tools that you will need to solve problems when a solution is not known.

Second, this uses interesting features of C++ not taught in our course because these features are not necessary to understand programming. Specifically, you will see, for example, arr[i++]. The post-autoincrement operator does return a value, just like the unary negation operator -x returns a value, specifically, the negation of whatever value x has. i++ increments the variable i, but returns the original value, while ++i (the pre-autoincrement operator) increments the value of i first and returns the updated value.

Third, this is fine, but the code is a little sub-optimal from a professional point of view. I would suggest the following solution is slightly better:

#include 

// Function declarations
int main();
void merge_arrays(
  int array1[], std::size_t capacity1,
  int array2[], std::size_t capacity2
  int merged[],
);

// Function definitions
int main() {
     const int M{5};
     const int N{4};

     int arr1[M]{1, 3, 5, 7, 9};
     int arr2[N]{2, 4, 6, 8};
     int merged[M + N];

     merge_arrays( arr1, M, arr2, N, merged );

    std::cout << "Merged Array: ";

    for (int i = 0; i < M + N; i++) {
        std::cout << merged[i] << " ";
    }

    std::cout << std::endl;

    return 0;
}

void merge_arrays(
  int array1[], std::size_t capacity1,
  int array2[], std::size_t capacity2
  int merged[],
) {
    // Set each index to the first entry
    // of each of the three arrays
    std::size_t idx1{ 0 };
    std::size_t idx2{ 0 };
    std::size_t    m{ 0 };

    // Merge the arrays in sorted order
    while ( (idx1 < capacity1) && (idx2 < capacity2) ) {
        if ( array1[idx1] <= array2[idx2] ) {
            merged[m] = array1[idx1];
            ++m;
            ++idx1;
        } else {
            merged[m] = array2[idx2];
            ++m;
            ++idx2;
        }
    }

    // Copy any remaining elements from 'array1'
    while ( idx1 < capacity1 ) {
        merged[m] = array1[idx1];
        ++m;
        ++idx1;
    }

    // Copy any remaining elements from 'array2'
    while ( idx2 < capacity2 ) {
        merged[m] = array2[idx2];
        ++m;
        ++idx2;
    }
}

Acknowledgements

Thank you to Dmitri Artamonov who shared two of the stories above and John Jekel who shared an experience from an upper-year course.