Question

I'm using class templates which contain virtual functions in my current project, and I stumbled upon a problem I can't overcome on my own.

  1. Class templates cannot have their member function bodies split from class definition in .hpp file because of linker errors. I don't want to instantiate my templates for each new type I'm abut to use, so all that's left is to leave them inlined. This is absolutely fine as they are 1-2 lines long most of the time, so I'm not going to experience any code bloat.
  2. On the other hand, gcc creates vtable for a polymorphic class in .cpp file that has definition of the first non-inline function that is declared in the class definition. Since I have all member functions inline, I'm getting undefined reference to vtable, or no RTTI symbol found for my class in GDB.

Please consider 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'm getting:

can't find linker symbol for virtual table for `test<int>' value

when debugging with GDB.

How do I force my compiler to put vtable in a specific cpp file when all class functions are inline?


EDIT:

Since the example provided above didn't illustrate the problem, here's my original code.

The class that's 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

Required 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;
}

Error I'm getting is:

RTTI symbol not found for class 'convertibleTo<std::string, convertibleTo_detail::default_converter>::holder<int>'

it's shown when I go inside i.get(), and then inside holder's get().


EDIT: Moved the full source from pastebin here, as per suggestion


Since the last two comments suggested that this is a GDB bug, how do I check this myself next time?

  1. In case GDB complains about missing vtable - would confirming that I can access every virtual member through a reference to ABC initialized with derived class be enough to confirm that everything is OK?
  2. In case GDB complains about missing RTTI symbol - would calling typeid() on the reference to ABC initialized with derived class to be enough to confirm that the RTTI symbol is, in fact, present?
Was it helpful?

Solution

Your code (the full version, with two header files and main.C) compiles and links without any errors for me, with gcc 4.8.3, with the default options (except for -std=c++11, to enable C++11 mode).

I even loaded the resulting executable into gdb. gdb swallowed it without any issues.

I don't see anything wrong here.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top