QT lambda slots

I’ve toyed with QT connections trying to create anonymous function when connecting a slot to a signal. For instance, I am trying to do the following:

menu->addAction( QIcon("some_icon.png"), "Open", this, SLOT(OnOpenTriggered()) );

So far so good, but sometime I find it cumbersome to define all the slots. Create a declaration, a definition, etc…

I would like to declare the body right there! Here’s what I would like to do using std::function or a c++ lambda:

menu->addAction( QIcon("some_icon.png"), "Open", _Q, _Q->Call( [this](){
    // body here...
    this->showNormal(); // e.g.
} ) );

I’ve achieved that by creating a global object named _Q which is responsible to maintained the QT meta object information about all connection that needs to be called.

Here’s the header of the class in question:

class GlobalQTReceiver : public QObject
{
	Q_GADGET;
public:

	typedef std::function<void()> Func;

	GlobalQTReceiver();

	const char* Call(Func slotCallback);

protected:

	virtual const QMetaObject *metaObject() const override;
	virtual int qt_metacall(QMetaObject::Call, int, void **) override;
	virtual void* qt_metacast(const char *_clname) override;
	
protected Q_SLOTS:

	void func1();

private:

	struct FuncInfo {
		std::string name;
		std::string slotName;
		Func callback;
	};

	void FillMetaStructs();

	int mNextId;
	std::vector<FuncInfo> mFuncs;
	std::vector<uint> mMetaData;
	std::vector<char> mMetaString;
	QMetaObject mMetaObject;
};

extern QTUtils::Internal::GlobalQTReceiver* _Q;

And here’s the implementation:


GlobalQTReceiver::GlobalQTReceiver()
	: mNextId(0)
{
	FillMetaStructs();
}

const char* GlobalQTReceiver::Call( std::function<void()> slotCallback )
{
	std::stringstream ss;
	ss << "func" << mNextId++ << "()";

	FuncInfo fi;
	fi.name = ss.str();
	fi.slotName = "1"+fi.name;
	fi.callback = slotCallback;

	mFuncs.push_back( fi );
	FillMetaStructs();
				
	return mFuncs.back().slotName.c_str();
}

int GlobalQTReceiver::qt_metacall(QMetaObject::Call call, int id, void** args)
{
	Q_ASSERT( call == QMetaObject::InvokeMetaMethod );

	id = QObject::qt_metacall(call, id, args);
	if (id < 0)
		return id;
	
	Func slotCallback = mFuncs[id].callback;
	id -= mFuncs.size();

	slotCallback();

	return id;
}

void* GlobalQTReceiver::qt_metacast( const char *_clname )
{
	Q_ASSERT( !"not supported" );
	return nullptr;
}

const QMetaObject* GlobalQTReceiver::metaObject() const 
{
	return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &mMetaObject;
}

void GlobalQTReceiver::FillMetaStructs()
{
	const char objectName[] = "QTUtils::Internal::GlobalQTReceiver";
	mMetaString.clear();
	mMetaString.insert(mMetaString.begin(), objectName, objectName+sizeof(objectName));
	mMetaString.push_back('\0');

	  /* content:
	  6,       // revision
		0,       // classname
		0,    0, // classinfo
		7,   14, // methods
		0,    0, // properties
		0,    0, // enums/sets
		0,    0, // constructors
		0,       // flags
		0,       // signalCount
		*/
	mMetaData.clear();
	mMetaData.push_back( 6 ); // revision
	mMetaData.push_back( 0 ); // classname
	mMetaData.push_back( 0 ); mMetaData.push_back( 0 ); // classinfo
	mMetaData.push_back( mFuncs.size() ); mMetaData.push_back( 14 ); // methods
	mMetaData.push_back( 0 ); mMetaData.push_back( 0 ); // properties
	mMetaData.push_back( 0 ); mMetaData.push_back( 0 ); // enum/sets
	mMetaData.push_back( 0 ); mMetaData.push_back( 0 ); // constructors
	mMetaData.push_back( 0 ); // flags
	mMetaData.push_back( 0 ); // signalCount

	// slots: signature, parameters, type, tag, flags
	int offset = sizeof(objectName)+1;
	for (auto it = mFuncs.begin(), end = mFuncs.end(); it != end; ++it)
	{
		size_t funcNameLength = it->name.size();
		const char* funcName = it->name.c_str();
		mMetaString.insert(mMetaString.end(), funcName, funcName+funcNameLength);
		mMetaString.push_back('\0');

		mMetaData.push_back( offset ); // signature
		mMetaData.push_back( 36 ); // parameters
		mMetaData.push_back( 36 ); // type
		mMetaData.push_back( 36 ); // tag
		mMetaData.push_back( 0x09 ); // flags
		offset += it->name.size()+1;
	}

	// eod
	mMetaData.push_back( 0 ); 

	mMetaObject.d.superdata  = &QObject::staticMetaObject;
	mMetaObject.d.stringdata = &mMetaString[0];
	mMetaObject.d.data       = &mMetaData[0];
	mMetaObject.d.extradata  = nullptr;
}

The idea here is that we maintain the QMetaObject needed to dispatch the call, when qt_metacall is called, to the proper anonymous functions. The magic is done in FillMetaStructs() to maintain the meta object structure each time a new connection is made when Call(...) is called.


Written on April 11, 2013