Search code examples
c++data-structureslinked-list

How to handle invalid data type being inserted into custom container


I have a custom Linked List container that I am currently testing. One of my tests is to first build the container to accept uint8_t types, then attempt to insert a float type and trigger an exception. The problem is that in my append function, the float gets appended without triggering my exception block. The actual contents of the node where the float is stored don't show when I print the data, but the size of the Linked list changes after the append, so it does seem the data is getting appended.

How can I check the types in such a way that I force a runtime error to get thrown in this particular case?

Here's my append function code and the test that's failing.

...
template <typename T>
        void libdsa::libstructures::LinkedList<T>::append(T datum)
        {
            // Triggers even during a valid insertion.
            // if (typeid(datum) != typeid(this))
            // {
            //    throw std::runtime_error("Invalid type being inserted.");
            // }

            // Fill the head node if it's empty. Otherwise append a new node.
            if (_head == nullptr)
            {
                try
                {
                    _head = new libdsa::libstructures::Node<T>(datum);
                    _head->_next = _head;
                    _head->_prev = _head;
                }
                catch(const std::exception& e)
                {
                    std::cerr << e.what() << '\n';
                }
            }
            else
            {
                try
                {
                    libdsa::libstructures::Node<T> *current = _head;

                    // Create the new node
                    libdsa::libstructures::Node<T> *newNode = new libdsa::libstructures::Node<T>(datum);

                    // Go to the end of the list
                    while (current->_next != _head)
                    {
                        current = current->_next;
                    }

                    // Append the new node and set the directional pointers.
                    current->_next = newNode;
                    newNode->_prev = current;
                    newNode->_next = _head;
                }
                catch(const std::exception& e)
                {
                    std::cerr << e.what() << '\n';
                }
            }

            ++_size;
        }
...
TEST(LinkedList, testInvalidAppend)
{
    std::vector<uint8_t> data = {'C', 'O', 'D', 'E'};
    auto list = setup(data);

    float temp = 23.22;
    list.append(temp);

    list.print();
    ASSERT_EQ(data.size(), list.getSize());
}
libdsa::libstructures::LinkedList<uint8_t> setup(std::vector<uint8_t> &data)
{
    libdsa::libstructures::LinkedList<uint8_t> list;

    for (size_t i = 0; i < data.size(); ++i)
    {
        list.append(data[i]);
    }
    
    return list;
}

I'd like an exception (or runtime_error) to be thrown when this invalid action is done, but neither are being triggered at the moment either with or without my conditional statements.

Initially, I attempted to us a try-catch block thinking that the compiler would catch it for me and print out the exception. However, it did not trigger it and allocated the data block. The other option I tried was to get the typeids of both the datum being inserted and the type of the container at runtime, but this caused an insertion failure in a successful case (where the type of the data and the container were actually the same).

Solution Update

Following the first method of PaulMcKenzie's answer, I was able to get it working so the template type T was compared to whatever was passed into the append function by the user, which became template type K. Below is the new function declaration with my container class and its definition.

...
template <typename T>
class LinkedList
{
public:
    /// @brief Default constructor.
    LinkedList() = default;

    /// @brief Appends a new data instance to the end of the list.
    /// @param datum Data instance to be appended.
    template <typename K>
    void append(K datum);
...
...
template <typename T>
template <typename K>
void libdsa::libstructures::LinkedList<T>::append(K datum)
{         
    if constexpr (!std::is_same_v<T, K>)
    {
        throw std::runtime_error("Invalid type being appended.");
    }
...

This now allows the container to be any data type and for that type to be compared to the type of a data instance the user wants to insert. After testing his method with the updates, it appears append() is now able to force the user to only insert data instances of the same type as the container. Many thanks to everyone for taking the time to help!


Solution

  • The issue is that the float has already been converted to a uint8_t at runtime, thus you cannot check this without a compile-time solution.

    If you are using C++17 or greater, you can take advantage of constexpr if:

    template <typename T>
    void libdsa::libstructures::LinkedList<T>::append(T datum)
    {
        if constexpr (!std::is_same_v<T, uint8_t>)
        {
            std::cout << "This is an invalid type " << datum << "\n";
            return;  // or throw an exception
        }
        //...
    }
    

    If the type is not uint8_t, the if block is true and will execute the code.

    Here is a live example


    If you want to stop the compilation and not produce an executable at all if the wrong type is sent, you can use static_assert (which works with C++11):

    #include <cstdint>
    //... 
    template <typename T>
    void libdsa::libstructures::LinkedList<T>::append(T datum)
    {
        // This will stop the compilation if the wrong type is sent   
        static_assert(std::is_same_v<T, uint8_t>, "It is not a uint8_t");
        //...
    }
    

    Here is a live example