How can I create a method with a variable number of arguments in a class with Q_OBJECT? [duplicate]
Asked Answered
Q

0

0

This post is not just about unresolved linker symbols in general, but about a variadic function in a class with a Q_OBJECT macro. The macro creates a lot of components automatically generated by the moc precompiler. For reasons not obvious in my code, it is dredging up errors about obscure symbols generated by the moc compiler; when these errors to not occur when component parts of this code are compiled separately! Note that none of the symbols particular to my source occur in the error messages. Questions of this nature are particularly thorny and arcane to debug. (It was wrongly flagged as a dupe of ? about unresolved external symbols in general. It is not a case where I have simply missed defining some procedure in my code, but referenced it.)

I'm trying to create an object that both has Qt events, and has a function that can accept a variable number of arguments. This is a minimal example, although lacking in the events, but with the QObject stuff needed for the events. The problem is that it generates a link error. It's based on the first answer to this SO ?: "Variable number of arguments in C++?"

#include <iostream>
#include <string>
#include <initializer_list>
#include <qobject.h>
#include <qdebug.h>

class aClass : public QObject
{
    Q_OBJECT

public:
    template <typename T>
    void func(T t)
    {
        qDebug() << t;
    }

    template<typename T, typename... Args>
    void func(T t, Args... args) // recursive variadic function
    {
        qDebug() << t;

        func(args...) ;
    }
};

int main()
{
    QString str1( "Hello" );

    aClass a;
    a.func(1, 2.5, 'a', str1);
}

The errors I get are:

main.obj:-1: error: LNK2001: unresolved external symbol "public: virtual struct QMetaObject const * __cdecl aClass::metaObject(void)const " (?metaObject@aClass@@UEBAPEBUQMetaObject@@XZ)
main.obj:-1: error: LNK2001: unresolved external symbol "public: virtual void * __cdecl aClass::qt_metacast(char const *)" (?qt_metacast@aClass@@UEAAPEAXPEBD@Z)
main.obj:-1: error: LNK2001: unresolved external symbol "public: virtual int __cdecl aClass::qt_metacall(enum QMetaObject::Call,int,void * *)" (?qt_metacall@aClass@@UEAAHW4Call@QMetaObject@@HPEAPEAX@Z)

If I remove the QObject stuff, it works. If the functions are outside the class, it works. Is there a way to have both in the same class? I want to have three arguments of predetermined types as the first arguments, not of the same types, and then from zero to any number of QString arguments after that. This is for a project in C++11, but a C++17 answer would be acceptable, too.

If I have the class definition in the header, but the function bodies in the main file, it gives a link error:

main.h

#ifndef MAIN_H
#define MAIN_H

#include <qobject.h>
#include <qdebug.h>

class aClass : public QObject
{
    Q_OBJECT

public:
    template <typename T>
    void func(T t);

    template<typename T, typename... Args>
    void func(T t, Args... args); // recursive variadic function
};

#endif // MAIN_H

main.cpp:

#include <qdebug.h>
#include "main.h"

template <typename T>
void func(T t)
{
    qDebug() << t;
}

template<typename T, typename... Args>
void func(T t, Args... args) // recursive variadic function
{
    qDebug() << t;

    func(args...) ;
}

int main()
{
    QString str1( "Hello" );

    aClass a;
    a.func(1, 2.5, 'a', str1);
}

I get the link error:

main.obj:-1: error: LNK2019: unresolved external symbol "public: void __cdecl aClass::func<int,double,char,class QString>(int,double,char,class QString)" (??$func@HNDVQString@@@aClass@@QEAAXHNDVQString@@@Z) referenced in function main

It complains about a missing aClass::func<int,double,char,class QString>(int,double,char,class QString) function.

If I put the function bodies in the class declaration in the header, it works:

main.h:

#ifndef MAIN_H
#define MAIN_H

#include <qobject.h>
#include <qdebug.h>

class aClass : public QObject
{
    Q_OBJECT

public:
    template <typename T>
    void func(T t)
    {
        qDebug() << t;
    }

    template<typename T, typename... Args>
    void func(T t, Args... args) // recursive variadic function
    {
        qDebug() << t;

        func(args...) ;
    }
};

#endif // MAIN_H

main.cpp:

#include <qdebug.h>
#include "main.h"

int main()
{
    QString str1( "Hello" );

    aClass a;
    a.func(1, 2.5, 'a', str1);
}

This is evidently related to "Why can templates only be implemented in the header file?"; although one would be misled into thinking it was some weirdness due to the moc precompiler - especially since all three of the original errors concerned metaobjects or a metacast.

Quadratics answered 23/4, 2023 at 3:28 Comment(9)
You have to #include the file generated by moc at the end of your source file. Please show your exact compilation and link commands, including the invocation of moc.Postwar
That sounds reasonable. I don't have any .moc files in my project dir, including the build directories. I'm using Qt Creator. I'm sure I can figure out how to generate them ...Quadratics
Tools such as Qt Creator will not run moc on .cpp files by default. You probably could persuade them to do so but why? Just move the definition of your class to a separate .h file.Postwar
Bizarre. When I have the function bodies in the class in the header, it works. When I have them in main.cpp, it gives me the link errors. Copying moc_predefs.h and including it at the end of main.cpp, and including moc_main.cpp gets it down to a single link error. Evidently, I can solve my problem by having short routines with their bodies in the header file with the class definitions, and the larger bodies in a cpp file. Not sure this answer was given in the supposed dupes. Would take some time. The linker warns about "Object specified move more than once." Huge thx, tho!Quadratics
You have several unrelated issues, don't clump them all together. "It gives me the link errors" is not a suitable description of a problem. You need to show the actual error messages. I recommend asking a separate question for each issue you have.Postwar
You may want to look at #495521Postwar
It seemed fairly atomic to me. It worked with the QObject stuff without the variadic function (in the class that caused the problem for me). It worked with out the QObject stuff. The problem was why the two didn't work together. I was unaware of the need for template bodies to be in headers in C++ in general, as in the referenced ?. When the template declaration was in the header, but the body in the .cpp, it didn't work. The linker warning suggests it may have to do with conflicting move constructors. I DID give the link errors in the first draft! OK. Dupe of 495021 then.Quadratics
Do you get the same error messages? No? These are different problems. I cannot possibly guess what they are without seeing the full source code and the full error messages that result from compiling this code. "I do this, I get errors, I do that, I also get errors" is not a valid problem description. You need to tell what you are doing by showing the code, and you need to tell which exact errors you are getting.Postwar
There u go. Somehow, it doesn't the function with the example parameters, but only if the function body is in the .cpp file. I know what to do now. I'm getting sleep. Feel free to remove the first paragraph if you want to reopen this now answered ?.Quadratics

© 2022 - 2025 — McMap. All rights reserved.