0

I know what I want, but I don't know how to tell the compiler what I want. I have split declarations and method definitions in .h and .cpp file, but the .cpp file gets included in the header, so all I did was separate declarations and definitions.

header file (avltree.h) - it includes the source file!:

template < class KEY_T, class DATA_T >
class CAvlTree {
    public:

        //----------------------------------------

        template < class KEY_T, class DATA_T >
        class CNode;

        typedef CNode< KEY_T, DATA_T> * tNodePtr;

        template < class KEY_T, class DATA_T >
        class CNode {
            public:
                KEY_T       key;
                DATA_T      data;
                CNode< KEY_T, DATA_T > *    left;
                CNode< KEY_T, DATA_T > *    right;
                char        balance;

                CNode() : left(nullptr), right(nullptr), balance(0) {}

                CNode(KEY_T key, DATA_T data) : 
                    key (key), data (data), left(nullptr), right(nullptr), balance(0) {}
            };

        //----------------------------------------

        template < class KEY_T, class DATA_T >
        struct tAvlInfo {
            CNode< KEY_T, DATA_T> * root;
            CNode< KEY_T, DATA_T> * current;
            KEY_T       key;
            bool        isDuplicate;
            bool        branchChanged;
        };

        typedef bool (* tNodeProcessor) (CNode< KEY_T, DATA_T> * nodePtr);

    private:

        tAvlInfo< KEY_T, DATA_T >   m_info;

        //----------------------------------------

    public:

        DATA_T* Find(KEY_T& key);

    private:

        CNode< KEY_T, DATA_T> * AllocNode(void);
};

#include "avltree.cpp"

source file (avltree.cpp):

template < typename KEY_T, typename DATA_T >
DATA_T* CAvlTree< KEY_T, DATA_T >::Find (KEY_T& key)
E0276   name followed by '::' must be a class or namespace name
{
   CNode* root;

   for (CNode* node = m_info.root; node; ) {
       if (key < node->key)
           node = node->left;
       else if (key > root->key)
           node = node->right;
       else {
           m_info.current = node;
           return &node->data;
       }
   }
return nullptr;
}


template < typename KEY_T, typename DATA_T >
CNode* CAvlTree< KEY_T, DATA_T >::AllocNode (void)
E0020   identifier "CNode" is undefined
E0864   CAvlTree is not a template
{
    if (m_info.current = new CNode < KEY_T, DATA_T >) {
        m_info.branchChanged = true;
        return m_info.current;
    }
    return nullptr;
}

What the compiler says (VS 2019 community, c++2020 enabled):

E0276   name followed by '::' must be a class or namespace name
E0020   identifier "CNode" is undefined
E0864   CAvlTree is not a template

I have no bloody clue how to write this down the correct way. Please enlighten me.

I have similar code for a template class with a single typename which works, but cannot conclude from that how to do this for two typenames.

Using the tNodePtr type instead of CNode< KEY_T, DATA_T > * in my source code also doesn't work.

Btw, I know that the compiler doesn't "transfer" KEY_T and DATA_T from the CAvlTree declaration just because I am using the same names.

Razzupaltuff
  • 2,250
  • 2
  • 21
  • 37
  • 1
    `source file (avltree.cpp):` for template is suspicious, see [why-can-templates-only-be-implemented-in-the-header-file](https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file) – Jarod42 Sep 29 '21 at 15:24
  • [Tangent] You really shouldn't call your template definition file a cpp file. cpp files are ment to be compiled, and yours isn't. I'd call it a tpp or ipp file to distinguish that. – NathanOliver Sep 29 '21 at 15:24
  • 1
    just a suggestion - don't call the implementation file *.cpp - IDEs/build systems will try and compile them - it call it *.imp or *.inl or something similar. – Richard Critten Sep 29 '21 at 15:25
  • 1
    How should the compiler know what template arguments to choose for `CNode` (at `CNode* root;` and other places in the function definitions)? What do you expect that `KEY_T` and `DATA_T` of `template < class KEY_T, class DATA_T > class CNode;` should be? – t.niese Sep 29 '21 at 15:29
  • While I agree that using `.cpp` for the file name of the template definitions is a bad idea, the problem the compiler complains about is not related to the current duplicate. – t.niese Sep 29 '21 at 15:31
  • 1
    @t.niese I agree as well and have reopened the Q. – NathanOliver Sep 29 '21 at 15:34
  • What you have is a header file that includes another header file. The second header file has a `.cpp` suffix but the suffix is irrelevant. It is only mildly confusing. There is a convention to give files with template implementations a different suffix, often `.inc`. – n. m. could be an AI Sep 29 '21 at 15:41
  • @Jarod: The cpp file gets included in the header file. I just split declarations and definitions. I stated this very clearly in my question. – Razzupaltuff Sep 29 '21 at 15:49
  • @Razzupaltuff naming it `.cpp` is still suspicious and can be problematic depending on the toolchain you use. See other comments. – t.niese Sep 29 '21 at 15:52
  • It works for another class I wrote (linked list class). I built this class after that example, but it just blows up on me ... I also know that just by naming every typename KEY_T and DATA_T doesn't mean the compiler simply matches them. It does when I e.g. pass them to tAvlInfo < KEY_T, DATA_T > which in turn does CNode< _K, _D > * root; I renamed a few things to make clearer that I am aware of that, and also used CNode< _K, _T > * in the included file (which I renamed to avltree.inc, with the result of having no syntax highlighting in my editor anymore. Yay.) – Razzupaltuff Sep 29 '21 at 16:02

1 Answers1

4

Your problem is the:

template < class KEY_T, class DATA_T >
class CNode;

Those KEY_T and DATA_T are unrelated to the one of CAvlTree.

clang and gcc even emit an error for those because of name shadowing (I don't know if VS does that too).

Due to CNode being a class template, the compiler expects you to provide template arguments for it (like you did for CNode< KEY_T, DATA_T> * AllocNode(void);) at all places where you use CNode.

But if you use KEY_T and DATA_T of CAvlTree for every CNode in your code, then the question is why did you define CNode as a class template in the first place.

And this part of the code indicates that you don't want CNode to be a class template:

template < class KEY_T, class DATA_T >
class CNode;

typedef CNode< KEY_T, DATA_T> * tNodePtr;

// …
CNode< KEY_T, DATA_T> * AllocNode(void);

The same seems to be true for template < class KEY_T, class DATA_T > struct tAvlInfo.

Due to that, I would assume that the code you want to have should look like this:

template <class KEY_T, class DATA_T>
class CAvlTree {
public:
  //----------------------------------------
  class CNode {
  public:
    KEY_T key;
    DATA_T data;
    CNode *left;
    CNode *right;
    char balance;

    CNode() : left(nullptr), right(nullptr), balance(0) {}

    CNode(KEY_T key, DATA_T data)
        : key(key), data(data), left(nullptr), right(nullptr), balance(0) {}
  };

  //----------------------------------------

  struct tAvlInfo {
    CNode *root;
    CNode *current;
    KEY_T key;
    bool isDuplicate;
    bool branchChanged;
  };

  typedef bool (*tNodeProcessor)(CNode *nodePtr);

private:
  tAvlInfo m_info;

  //----------------------------------------

public:
  DATA_T *Find(KEY_T &key);

private:
  CNode *AllocNode(void);
};

template <typename KEY_T, typename DATA_T>
DATA_T *CAvlTree<KEY_T, DATA_T>::Find(KEY_T &key) {
  CNode *root;

  for (CNode *node = m_info.root; node;) {
    if (key < node->key)
      node = node->left;
    else if (key > root->key)
      node = node->right;
    else {
      m_info.current = node;
      return &node->data;
    }
  }
  return nullptr;
}

template <typename KEY_T, typename DATA_T>
auto CAvlTree<KEY_T, DATA_T>::AllocNode(void) -> CNode * {
  if (m_info.current = new CNode) {
    m_info.branchChanged = true;
    return m_info.current;
  }
  return nullptr;
}

And using the auto identifier ( argument-declarations... ) -> return_type syntax allows you to use the CName name directly. Otherwise, you would need to write template <typename KEY_T, typename DATA_T> typename CAvlTree<KEY_T, DATA_T>::CNode* CAvlTree<KEY_T, DATA_T>::AllocNode(void)

t.niese
  • 39,256
  • 9
  • 74
  • 101
  • Man, I didn't think of not having to make CNode a template class to use KEY_T and DATA_T inside of it ... very good hint! As I look at it I cannot help it but facepalm to myself ... :D I was obviously making things more complicated than I had to. – Razzupaltuff Sep 29 '21 at 16:07
  • There are still some problems I cannot detect the cause of. Would you mind taking another look at my (edited) question? – Razzupaltuff Sep 29 '21 at 16:27
  • @Razzupaltuff don't edit the original question in such a way. Changing the code that way results in a completely different question, and will result in the existing answers not match the question anymore. If you have a new question, then create a new one. You could refer to this question in the new one if you think that this is helpful for the readers. – t.niese Sep 29 '21 at 16:30
  • @Razzupaltuff regarding what you have asked in that edit, check the last paragraph in my answer. That is the same problem. – t.niese Sep 29 '21 at 16:34
  • Yeah, I actually figured it myself pretty quickly. Thanks for the help. :) – Razzupaltuff Sep 29 '21 at 21:16