Home > Software design >  Need specific instruction set to solve the conflict between two kinds of class constructors (one of
Need specific instruction set to solve the conflict between two kinds of class constructors (one of

Time:08-22

A class (dLinkedList) need to have different c-tors: [please don't suggest to use STL containers!]

explicit dLinkedList(const int value);
dLinkedList(const dLinkedList &rhs);
explicit dLinkedList(size_t numberOfNode, int initializationValue);
dLinkedList(const std::initializer_list<int> &arg);

However, the c-tor with initializer_list seems to be not working. For example, if we construct a class like this:

dLinkedList test1 {10, 108}; // intention is to create 10 elements of 108 value each

But my question is -- based on the arguments - this c-tor can well be considered as one with initializer_list<int>. How do I resolve this conflict?

The implementation of the last two constructors and the structure of the class dLinkedList are as follow:

class dLinkedList
{
public:

    //constructors will go here
    explicit dLinkedList(const int value)
    {
        createFirstNode(value);
        nodeCount = new size_t;
        updateNodeCount();
    }
    
    dLinkedList(const dLinkedList &rhs)
    {
        Node *temp = rhs.head;

        createFirstNode(temp->data);
        temp = temp->next;
        while(temp)
        {
            push_back(temp->data);
            temp = temp->next;
        }
        
        nodeCount = new size_t;
        updateNodeCount();
    }
    
    explicit dLinkedList(size_t numberOfNode, int initializationValue)
    {
        createFirstNode(initializationValue);
        for(size_t i = 1; i < numberOfNode;   i)
            push_back(initializationValue);
        
        nodeCount = new size_t;
        updateNodeCount();
    }
    
    /* constructor with std::initializer_list is not working!!
    dLinkedList(const std::initializer_list<int> &arg)
    {
        std::cout << "\n Here\t";
        if(arg.size() == 0) dLinkedList(1);
        else
        {
            for(auto it = arg.begin()   1; it != arg.end();   it)
            {
                push_back(*it);
            }
            
            nodeCount = new size_t;
            updateNodeCount();
        }
    }*/

    //class destructor will go here
    ~dLinkedList()
    {
        clear();
        delete nodeCount;
        nodeCount = nullptr;
    }

    //member functions will go here
    void push_back(int);                    // will attach a new node at the end of the list
    void push_front(int);                   // will insert a new node at the beginning of the list
    bool insertNode(int, int, bool, bool);  // will insert a new node after the existing node (true = first occurrence from the head with value int OTHERWISE if false, then from the tail.)
    bool deleteNode(int, bool);             // will delete the existing node (true = first occurrence from the head with value int OTHERWISE if false, then from the tail.)
    void pop_back();                         // will delete the last node in the list
    void pop_front();                       // will delete the first node in the list
    size_t size();                          // will return the number of nodes/elements - experimental feature
    void printList(bool);                   // will print the values of the data - (true for ordered list, false for reverse ordered list)
    void swap(dLinkedList &rhs);             // will swap this linked-list with rhs

    //operator overloading go here
    dLinkedList& operator = (const dLinkedList &rhs);
    dLinkedList& operator   (const dLinkedList &rhs);
    dLinkedList& operator  = (const dLinkedList &rhs);
    dLinkedList& operator >> (const size_t numberOfNodes);
    dLinkedList& operator << (const dLinkedList &rhs);

private:
    //defining the double linked-list structure
    struct Node
    {
        int data; //this is a generic place holder - will be replaced later with some actual data-structures
        Node *next;
        Node *previous;

        explicit Node(int x) : data(x), next(nullptr), previous(nullptr) {}
    };

    //member functions go here
    void createFirstNode(int val);  //will create the first node when the list is empty
    void clear();  // will be called when class destructor is called
    void updateNodeCount(); // keeps the nodeCount variable up-to-date
    bool empty(); // returns true if the list is empty

    //some experimental utility functions for internal use
    void ectomizeAndClip(Node*);
    Node *detectData(int, bool);
    void insertBefore(Node*, int);
    void insertAfter(Node*, int);

    //member variables go here
    Node *head {nullptr};
    Node *tail {nullptr};
    size_t *nodeCount {nullptr}; //experimental feature
    
};

CodePudding user response:

You do not need to do anything to "resolve the conflict". The language does it for you; initialization in the form

dLinkedList test1 {10, 108};

will always call an initializer_list constructor if possible, falling back to a non-initializer_list constructor only if there are no viable initializer_list constructor candidates.

The problem is that the resolution of the potential conflict did not produce the outcome that you wanted. You want it to be resolved in favour of the two-element constructor.

It is possible to simulate what you want by having the initializer_list constructor delegate to another function only if it has two elements:

explicit dLinkedList(size_t numberOfNode, int initializationValue) {
    initialize(numberOfNode, initializationValue);
}
dLinkedList(const std::initializer_list<int> &arg) {
    if (arg.size() == 2) {
        initialize(arg[0], arg[1]);
        return;
    }
    // otherwise: initialize the list using the values in `arg`
}
private:
void initialize(size_t numberOfNode, int initializationValue) {
    // create `numberOfNode` nodes with value `initializationValue`
}

However, I would strongly recommend that you not do this, because it will be terribly confusing for the user if the meanings of the following three declarations are not similar:

dLinkedList test1 {1};
dLinkedList test2 {2, 3};
dLinkedList test3 {4, 5, 6};

In fact, a constructor that takes the number of nodes as the first argument, and the value with which to initialize those nodes as the second argument, is a bad idea in general. It will lead to bugs when users forget which argument is which.

A better way is to create a struct that has the desired number of nodes and desired initial value as members:

struct dLinkedListInit {
    int numberOfNodes;
    int initialValue;
};
explicit dLinkedList(dLinkedListInit i) {
    // ...
}

With C 20 designated initializers, this constructor could be used as follows:

dLinkedList l({.numberOfNodes = 10, .initialValue = 108});

Also, strike the constructor explicit dLinkedList(const int value); since the initializer list constructor can provide the same functionality. It is good to make users write out the braces explicitly to make it obvious that the value inside the braces is meant to be interpreted as an element value.

  • Related