I am using class templates that contain virtual functions in my current project, and I came across a problem that I cannot overcome on my own.
- Class templates cannot share the bodies of the member members of the class definition in the .hpp file due to linker errors. I donβt want to create templates for every new type that I use, so all leave them nested. This is absolutely excellent, as they make up 1-2 lines in most of the time, so I am not going to try out any bloated code.
- On the other hand, gcc creates a vtable for a polymorphic class in a .cpp file with the definition of the first non-built-in function that is declared in the class definition. Since I have all the members of the inline function, I get an undefined reference to the vtable, or not, the RTTI character found for my class in GDB.
Pay attention to the following code:
template <typename T> struct Test { virtual void testMe() const = 0; virtual ~Test() = default; }; template <typename T> struct test : public Test<T> { virtual void testMe() const { std::cout << typeid(T).name() << std::endl; } virtual ~test() = default; }; int main() { test<int> t; Test<int>& T = t; T.testMe(); return 0; }
In this particular example, I get:
can't find linker symbol for virtual table for `test<int>' value
when debugging using GDB.
How to get my compiler to put vtable in a specific cpp file when all class functions are built-in?
EDIT
Since the above example does not illustrate the problem, here is my original code.
The class causing the problem:
#ifndef CONVERTIBLETO_H #define CONVERTIBLETO_H #include "convertibleTo_converters.h" #include <functional> template < typename IT, template <typename InterfaceType, typename ErasedType> class Converter = convertibleTo_detail::default_converter > class convertibleTo { public: typedef convertibleTo<IT, Converter> this_type; typedef IT InterfaceType; struct is_type_eraser_tag {}; private: class holder_interface { public: virtual InterfaceType get() const = 0; virtual void set(const InterfaceType&) = 0; virtual holder_interface* clone() const = 0; virtual ~holder_interface() {} }; template <typename ErasedType> class holder : public holder_interface { public: virtual InterfaceType get() const { return (Converter<InterfaceType, ErasedType>::convert(this->data)); } virtual void set(const InterfaceType& value) { this->data = (Converter<InterfaceType, ErasedType>::convert(value)); } virtual holder_interface* clone() const { return new holder(*this); } holder() = delete; holder(const holder& other): data(other.data) { } holder(ErasedType& d): data(d) { } virtual ~holder() = default; private: ErasedType& data; }; public: inline InterfaceType get() const { if (this->held) return this->held->get(); else return InterfaceType(); } inline void set(const InterfaceType& value) { if (this->held) this->held->set(value); } inline bool empty() const { return ! this->held; } convertibleTo<InterfaceType, Converter>& operator= (const convertibleTo<InterfaceType, Converter>& other) { if(this->held) delete this->held; this->held = other.held->clone(); return *this; } convertibleTo(): held(nullptr) { } template <typename T> explicit convertibleTo(T& data): held(new holder<T>(data)) { } convertibleTo( convertibleTo& other ): convertibleTo( const_cast<const convertibleTo&>(other)) { } convertibleTo( const convertibleTo& other ): held(nullptr) { if(other.held) this->held = other.held->clone(); } ~convertibleTo() { if (this->held) delete this->held; } private: holder_interface * held; }; #endif
Necessary helper classes:
#ifndef CONVERTIBLETO_CONVERTERS_H #define CONVERTIBLETO_CONVERTERS_H #include <string> #include <sstream> namespace convertibleTo_detail { template <typename InterfaceType, typename ErasedType> struct default_converter { static inline InterfaceType convert(const ErasedType& input) { return input; } static inline ErasedType convert(const InterfaceType& input) { return input; } }; template <typename T> struct default_converter<T, T> { static inline T convert(const T& input) { return input; } }; template <typename ErasedType> struct default_converter<std::string, ErasedType> { static inline std::string convert(const ErasedType& input) { default_converter<std::string, ErasedType>::prepareConverter(); default_converter<std::string, ErasedType>::converter << input; return default_converter<std::string, ErasedType>::converter.str(); } static inline ErasedType convert(const std::string& input) { default_converter<std::string, ErasedType>::prepareConverter(input); ErasedType result; default_converter<std::string, ErasedType>::converter >> result; return result; } private: static std::stringstream converter; struct SetExceptionFlagsOnce { SetExceptionFlagsOnce() { default_converter<std::string, ErasedType>::converter.exceptions(std::stringstream::failbit); } }; static void inline prepareConverter(std::string value = "") { static SetExceptionFlagsOnce setter; default_converter<std::string, ErasedType>::converter.clear(); default_converter<std::string, ErasedType>::converter.str(value); } }; template <typename ErasedType> std::stringstream default_converter<std::string, ErasedType>::converter; template <> struct default_converter<std::string, std::string> { static inline std::string convert(const std::string& input) { return input; } }; } #endif // CONVERTIBLETO_CONVERTERS_H
main.cpp:
#include <iostream> #include "convertibleTo.h" int main() { int I = 5; convertibleTo< std::string > i(I); std::cout << i.get() << std::endl; i.set("321"); std::cout << i.get() << std::endl; return 0; }
The error I get is:
RTTI symbol not found for class 'convertibleTo<std::string, convertibleTo_detail::default_converter>::holder<int>'
it displays when I go inside i.get () and then inside the get () holder.
EDIT . Relocated full source from pastebin here, as per suggestion
Since the last two comments have suggested that this is a GDB bug, how can I check this next time?
- If GDB complains about the lack of vtable - would you confirm that I can access each virtual member through a link to the ABC initialized by the derived class, enough to confirm that everything is in order?
- If GDB complains about the absence of an RTTI character, will typeid () invoke to refer to an ABC initialized by a derived class sufficient to confirm that the RTTI character is in fact present?