0

I have a problem with putting namespaces in separate files. Everything works for me when I write everything in one file, but when I try to create cpp+hpp files, that's where it all starts. Maybe I'm doing something wrong in the headerfile or linking the files incorrectly? I really don't know. I will be very grateful for your help.

Here is my error. On 86x_64 it also throws it out, but without mentioning the architecture

Undefined symbols for architecture arm64:
  "Huffman::operator<<(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, Huffman::EncodingMap const&)", referenced from:
      _main in main-4994c4.o
ld: symbol(s) not found for architecture arm64
// huffman.cpp
#include <iostream>
#include <string>
#include <queue>
#include <unordered_map>
#include <memory>

namespace Huffman
{
  struct TreeNode
  {
  private:
    struct Compare
    {
      bool operator()(std::shared_ptr<TreeNode> left, std::shared_ptr<TreeNode> right)
      {
        return left->frequency > right->frequency;
      }
    };

  public:
    char symbol;
    unsigned frequency;
    std::shared_ptr<TreeNode> left;
    std::shared_ptr<TreeNode> right;

    TreeNode(char symbol, unsigned frequency)
        : symbol(symbol), frequency(frequency) {}

    static std::shared_ptr<TreeNode> buildTree(const std::string &text)
    {
      std::unordered_map<char, unsigned> frequencyTable;
      for (char c : text)
      {
        ++frequencyTable[c];
      }

      std::priority_queue<std::shared_ptr<TreeNode>, std::vector<std::shared_ptr<TreeNode>>, Compare> queue;

      for (const auto &[symbol, frequency] : frequencyTable)
      {
        queue.push(std::make_shared<TreeNode>(symbol, frequency));
      }

      while (queue.size() > 1)
      {
        auto left = queue.top();
        queue.pop();

        auto right = queue.top();
        queue.pop();

        auto newNode = std::make_shared<TreeNode>('$', left->frequency + right->frequency);
        newNode->left = left;
        newNode->right = right;

        queue.push(newNode);
      }

      return queue.top();
    }
  };

  class EncodingMap
  {
  private:
    std::unordered_map<char, std::string> map;

    void build(std::shared_ptr<Huffman::TreeNode> node, std::string code)
    {
      if (node.get() == nullptr)
        return;

      if (node->left == nullptr && node->right == nullptr)
      {
        map[node->symbol] = code;
        return;
      }

      build(node->left, code + "0");
      build(node->right, code + "1");
    }

  public:
    EncodingMap(const std::string &text)
    {
      auto root = TreeNode::buildTree(text);
      build(root, "");
    }

    friend std::ostream &operator<<(std::ostream &os, const EncodingMap &map)
    {
      for (auto &&[key, value] : map.map)
      {
        std::cout << key << "=" << value << std::endl;
      }
      return os;
    }

    const std::string &operator[](char symbol)
    {
      return map[symbol];
    }
  };

  std::pair<std::string, EncodingMap> encode(const std::string &text)
  {
    EncodingMap map = EncodingMap(text);

    std::string encodedText;
    for (char c : text)
    {
      encodedText += map[c];
    }

    return std::make_pair(encodedText, map);
  }
};
// huffman.hpp
#ifndef HUFFMAN_H
#define HUFFMAN_H

#include <iostream>
#include <string>
#include <queue>
#include <unordered_map>
#include <memory>

namespace Huffman
{
  struct TreeNode
  {
  public:
    char symbol;
    unsigned frequency;
    std::shared_ptr<TreeNode> left;
    std::shared_ptr<TreeNode> right;

    TreeNode(char symbol, unsigned frequency)
        : symbol(symbol), frequency(frequency) {}

    static std::shared_ptr<TreeNode> buildTree(const std::string &text);
  };

  class EncodingMap
  {
  public:
    EncodingMap(const std::string &text);

    friend std::ostream &operator<<(std::ostream &os, const EncodingMap &map);

    const std::string &operator[](char symbol);
  };

  std::pair<std::string, EncodingMap> encode(const std::string &text);
};

#endif
// main.cpp
#include <iostream>
#include "./huffman.hpp"

int main()
{
  auto [encoded, map] = Huffman::encode("hello world");
  std::cout << encoded << std::endl;
  std::cout << map << std::endl;

  return 0;
}

I tried to build it with cmake, but I also built it myself for testing, and the error is really independent of my build system.

g++ ./src/main.cpp ./src/huffman.cpp -std=c++17
MelKam
  • 48
  • 5
  • 7
    This has nothing to do with namespaces. You would have the exact same issue if you removed the namespaces. Why are you defining `TreeNode` and `EncodingMap` again (and differently!!) in the `.cpp` file instead of including the `.hpp` file and then writing out-of-class definitions for the members? That's an ODR (one-definition rule) violation and you see the effect of that. – user17732522 May 17 '23 at 09:28
  • You should just include the .hpp file in the .cpp file, and not redeclare the struct and class. In the source file just add the functions that are not already defined in the header. – BoP May 17 '23 at 09:34
  • Delete the class definitions from `huffman.cpp` and add `#include "huffman.hpp"`. Not sure why you thought defining classes twice was a reasonable thing to do. C++ has a few quirks but defining the same thing twice is not necessary – john May 17 '23 at 09:52
  • There is no proper declaration for `std::ostream& Huffman::operator<<(std::ostream&, const Huffman::EncodingMap&)` in the header or the main TU. Refer to the 1st comment for the primary flaw in your design. – Red.Wave May 17 '23 at 09:52

1 Answers1

2

This is how you are supposed to split classes into header file and source files

// header file, x.h

#ifndef X_H
#define X_H

class X
{
     void f();
};

#endif

// source file, x.cpp

#include "x.h"

void X::f()
{
    // code here
}
john
  • 85,011
  • 4
  • 57
  • 81