0

I have the following project structure:

This a handler for the "IOPin" class:

//IOPinHandler class
//IOPinHandler.h

#include <type_traits>

class IOPin; //forward declaration required

class IOPinHandler
{
    public:

        explicit IOPinHandler() { }
        virtual ~IOPinHandler() { }
        void checkBool(const bool& b);
        void checkInt(const int& b);
        template<typename T>
        void modifyIOPinMember(IOPin& ioPin, const T& param);

};

//To avoid multiple definitions
#ifndef  _OD_

void IOPinHandler::checkBool(const bool& b)
{
    //Do stuff
}

void IOPinHandler::checkInt(const int& b)
{
    //Do stuff
}
#endif

The following is the .tpp file for the definition of modifyIOPinMember member.

//IOPinHandler class
//IOPinHandler.tpp

template<typename T>
void IOPinHandler::modifyIOPinMember(IOPin& ioPin, const T& param)
{
        if constexpr(std::is_same_v<T, int>)
        {
            checkInt(param);
            ioPin.m2 = param;
        }
        else if constexpr(std::is_same_v<T, bool>)
        {
            checkBool(param);
            ioPin.m1 = param;
        }
}

The following is the "IOPin" class, the one meant to be handled by the class above. Since IOPinHandler's modifyIOPinMember member requires to know the definition of "IOPin" (its complete type) then, the IOPinHandler.tpp file is included in IOPin.h file as follows:

//IOPin class
//IOPin.h

//To avoid multiple definitions
#define _OD_
#include "IOPinHandler.h"

class IOPin
{
    public:
        explicit IOPin(const bool& b, const int& n):m1(b), m2(n) { _handler = new IOPinHandler; }
        void setInt(const int& n) { _handler->modifyIOPinMember(*this, n); }
        void setBool(const bool& b) { _handler->modifyIOPinMember(*this, b); }

    private:
        bool m1{false};
        int m2{0};
        IOPinHandler* _handler{nullptr};
        friend class IOPinHandler;

};

#include "IOPinHandler.tpp"

The problem is that calling either setInt or SetBool methods, result in a compile time error:

//main.cpp

#include "IOPin.h"

IOPin a(false, 0);

int main()
{
    a.setInt(89);
    a.setBool(true);

    return 0;
}

This is the error:

/usr/bin/ld: /tmp/ccpKv7HW.o: in function `void IOPinHandler::modifyIOPinMember<int>(IOPin&, int const&)':
main.cpp:(.text._ZN12IOPinHandler17modifyIOPinMemberIiEEvR5IOPinRKT_[_ZN12IOPinHandler17modifyIOPinMemberIiEEvR5IOPinRKT_]+0x27): undefined reference to `IOPinHandler::checkInt(int const&)'
/usr/bin/ld: /tmp/ccpKv7HW.o: in function `void IOPinHandler::modifyIOPinMember<bool>(IOPin&, bool const&)':
main.cpp:(.text._ZN12IOPinHandler17modifyIOPinMemberIbEEvR5IOPinRKT_[_ZN12IOPinHandler17modifyIOPinMemberIbEEvR5IOPinRKT_]+0x27): undefined reference to `IOPinHandler::checkBool(bool const&)'
collect2: error: ld returned 1 exit status

What am I missing over here?

I know that a solution is to create a "IOPinHandler.cpp" file and put there the definitions for "checkBool" and "checkInt" methods, however I dont want to have a separate .cpp file only for that.

Thanks in advance.

François Andrieux
  • 28,148
  • 6
  • 56
  • 87
Juan_David
  • 126
  • 2
  • 11
  • 7
    Defining functions or class members in header files, and relying on condition compilation settings to include them or not, always ends in tears. There are only three simple rules to follow: 1) Non-template declarations in header files, 2) Template definitions in header files, 3) Non-template definitions in .cpp files. Avoid all the headaches. Keep it simple. – Sam Varshavchik Feb 12 '23 at 16:26
  • 1
    Take out the `#ifndef _OD_`. Put `inline` in front of both `checkBool` and `checkInt` definitions. – Eljay Feb 12 '23 at 16:28
  • Did you write `IOPinHandler.h`? – HolyBlackCat Feb 12 '23 at 16:31
  • side note: [What are the rules about using an underscore in a C++ identifier?](https://stackoverflow.com/q/228783/4581301) – user4581301 Feb 12 '23 at 17:58
  • Also note that each [translation unit](https://en.wikipedia.org/wiki/Translation_unit_(programming)) is compiled separately, so the `ifndef`s are also handled separately. This means when the linker gets around to putting the compiled object files together, each object file may have its own copy of what's "protected" by the `ifndef` – user4581301 Feb 12 '23 at 18:11
  • In my view there is no good reason for using a `tpp` file. Since, when you do use them, the `tpp` is included in the `h` file and nowhere else, it makes sense to me to just dispose of the `tpp` file and put everything in the `h` file directly. – john Feb 12 '23 at 18:32

1 Answers1

2

In C++, we almost never include the implementation file, only header (.h) files; and, if your class is templated, all class's function implementations should be in header only; no secondary file is needed or advised, and you should always use header guards for your header files, used as follows:

#ifndef ANY_UNIQUE_NAME // recommended related to header file name 
#define ANY_UNIQUE_NAME
//#includes <...>
//header code
#endif

Then you include headers when you need them.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
Shahrooz
  • 196
  • 1
  • 14