aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKitsune Ral <Kitsune-Ral@users.sf.net>2017-03-08 13:16:37 +0900
committerGitHub <noreply@github.com>2017-03-08 13:16:37 +0900
commitba5ca3c88a20926f2be06872f530d9da12d5a062 (patch)
tree1d8b69e433dacf51427b4e3dcab9545374c85116
parent2cc1fc52baa28c4896080069a985c200517ad7cd (diff)
parent81b6d940218a212a37e3c600c66fbe9a7476f433 (diff)
downloadlibquotient-ba5ca3c88a20926f2be06872f530d9da12d5a062.tar.gz
libquotient-ba5ca3c88a20926f2be06872f530d9da12d5a062.zip
Merge pull request #53 from Fxrh/kitsune-fix-adding-events
Fix adding events to the timeline
-rw-r--r--events/event.cpp24
-rw-r--r--events/event.h48
-rw-r--r--events/roommessageevent.cpp1
-rw-r--r--events/unknownevent.cpp2
-rw-r--r--jobs/roommessagesjob.cpp8
-rw-r--r--jobs/roommessagesjob.h2
-rw-r--r--jobs/syncjob.cpp3
-rw-r--r--jobs/syncjob.h33
-rw-r--r--logging_util.h63
-rw-r--r--room.cpp101
-rw-r--r--room.h9
-rw-r--r--util.h169
12 files changed, 270 insertions, 193 deletions
diff --git a/events/event.cpp b/events/event.cpp
index 43604b23..8ad56f1b 100644
--- a/events/event.cpp
+++ b/events/event.cpp
@@ -23,7 +23,7 @@
#include <QtCore/QDateTime>
#include <QtCore/QDebug>
-#include "../logging_util.h"
+#include "util.h"
#include "roommessageevent.h"
#include "roomnameevent.h"
#include "roomaliasesevent.h"
@@ -114,31 +114,31 @@ Event* Event::fromJson(const QJsonObject& obj)
bool Event::parseJson(const QJsonObject& obj)
{
d->originalJson = QString::fromUtf8(QJsonDocument(obj).toJson());
+ d->id = obj.value("event_id").toString();
+ d->roomId = obj.value("room_id").toString();
+ d->senderId = obj.value("sender").toString();
bool correct = (d->type != EventType::Unknown);
- if ( d->type != EventType::Unknown &&
- d->type != EventType::Typing &&
+ if ( d->type != EventType::Typing &&
d->type != EventType::Receipt )
{
- if( obj.contains("event_id") )
+ if (d->id.isEmpty())
{
- d->id = obj.value("event_id").toString();
- } else {
correct = false;
- qDebug() << "Event: can't find event_id";
+ qDebug() << "Event: can't find event_id; event dump follows";
qDebug() << formatJson << obj;
}
if( obj.contains("origin_server_ts") )
{
- d->timestamp = QDateTime::fromMSecsSinceEpoch(
+ d->timestamp = QDateTime::fromMSecsSinceEpoch(
static_cast<qint64>(obj.value("origin_server_ts").toDouble()), Qt::UTC );
- } else {
+ }
+ else if (d->type != EventType::Unknown)
+ {
correct = false;
- qDebug() << "Event: can't find ts";
+ qDebug() << "Event: can't find ts; event dump follows";
qDebug() << formatJson << obj;
}
}
- d->roomId = obj.value("room_id").toString();
- d->senderId = obj.value("sender").toString();
return correct;
}
diff --git a/events/event.h b/events/event.h
index 12b0ebd5..f60dfb64 100644
--- a/events/event.h
+++ b/events/event.h
@@ -60,52 +60,4 @@ namespace QMatrixClient
using Events = QVector<Event*>;
Events eventsFromJson(const QJsonArray& json);
-
- /**
- * @brief Lookup a value by a key in a varargs list
- *
- * The below overloaded function template takes the value of its first
- * argument (selector) as a key and searches for it in the key-value map
- * passed in a varargs list (every next pair of arguments forms a key-value
- * pair). If a match is found, the respective value is returned; otherwise,
- * the last value (fallback) is returned.
- *
- * All options should be of the same type or implicitly castable to the
- * type of the first option. Note that pointers to methods of different
- * classes are of different object types, in particular.
- *
- * Below is an example of usage to select a parser depending on contents of
- * a JSON object:
- * {@code
- * auto parser = lookup(obj.value["type"].toString(),
- * "type1", fn1,
- * "type2", fn2,
- * fallbackFn);
- * parser(obj);
- * }
- *
- * The implementation is based on tail recursion; every recursion step
- * removes 2 arguments (match and option). There's no selector value for the
- * fallback option (the last one); therefore, the total number of lookup()
- * arguments should be even: selector + n key-value pairs + fallback
- *
- * @note Beware of calling lookup() with a <code>const char*</code> selector
- * (the first parameter) - most likely it won't do what you expect because
- * of shallow comparison.
- */
- template <typename ValueT, typename SelectorT, typename KeyT, typename... Ts>
- ValueT lookup(SelectorT selector, KeyT key, ValueT value, Ts... remainingMapping)
- {
- if( selector == key )
- return value;
-
- // Drop the failed key-value pair and recurse with 2 arguments less.
- return lookup(selector, remainingMapping...);
- }
-
- template <typename SelectorT, typename ValueT>
- ValueT lookup(SelectorT/*unused*/, ValueT fallback)
- {
- return fallback;
- }
}
diff --git a/events/roommessageevent.cpp b/events/roommessageevent.cpp
index bb28d682..34315363 100644
--- a/events/roommessageevent.cpp
+++ b/events/roommessageevent.cpp
@@ -17,6 +17,7 @@
*/
#include "roommessageevent.h"
+#include "util.h"
#include <QtCore/QMimeDatabase>
#include <QtCore/QDebug>
diff --git a/events/unknownevent.cpp b/events/unknownevent.cpp
index 90551409..70dcfcbb 100644
--- a/events/unknownevent.cpp
+++ b/events/unknownevent.cpp
@@ -21,7 +21,7 @@
#include <QtCore/QJsonDocument>
#include <QtCore/QDebug>
-#include "../logging_util.h"
+#include "util.h"
using namespace QMatrixClient;
diff --git a/jobs/roommessagesjob.cpp b/jobs/roommessagesjob.cpp
index 1e53f601..7a0bc756 100644
--- a/jobs/roommessagesjob.cpp
+++ b/jobs/roommessagesjob.cpp
@@ -28,7 +28,7 @@ class RoomMessagesJob::Private
public:
Private() {}
- Events events;
+ Owning<Events> events;
QString end;
};
@@ -49,9 +49,9 @@ RoomMessagesJob::~RoomMessagesJob()
delete d;
}
-Events RoomMessagesJob::events()
+Events RoomMessagesJob::releaseEvents()
{
- return d->events;
+ return d->events.release();
}
QString RoomMessagesJob::end()
@@ -62,7 +62,7 @@ QString RoomMessagesJob::end()
BaseJob::Status RoomMessagesJob::parseJson(const QJsonDocument& data)
{
QJsonObject obj = data.object();
- d->events = eventsFromJson(obj.value("chunk").toArray());
+ d->events.assign(eventsFromJson(obj.value("chunk").toArray()));
d->end = obj.value("end").toString();
return Success;
}
diff --git a/jobs/roommessagesjob.h b/jobs/roommessagesjob.h
index 348217cc..fd6a131d 100644
--- a/jobs/roommessagesjob.h
+++ b/jobs/roommessagesjob.h
@@ -35,7 +35,7 @@ namespace QMatrixClient
FetchDirectory dir = FetchDirectory::Backwards, int limit=10);
virtual ~RoomMessagesJob();
- Events events();
+ Events releaseEvents();
QString end();
protected:
diff --git a/jobs/syncjob.cpp b/jobs/syncjob.cpp
index 554ac0f7..cec9595f 100644
--- a/jobs/syncjob.cpp
+++ b/jobs/syncjob.cpp
@@ -96,8 +96,7 @@ BaseJob::Status SyncJob::parseJson(const QJsonDocument& data)
void SyncRoomData::EventList::fromJson(const QJsonObject& roomContents)
{
- auto l = eventsFromJson(roomContents[jsonKey].toObject()["events"].toArray());
- swap(l);
+ assign(eventsFromJson(roomContents[jsonKey].toObject()["events"].toArray()));
}
SyncRoomData::SyncRoomData(QString roomId_, JoinState joinState_, const QJsonObject& room_)
diff --git a/jobs/syncjob.h b/jobs/syncjob.h
index be1d4776..b41c09d4 100644
--- a/jobs/syncjob.h
+++ b/jobs/syncjob.h
@@ -22,41 +22,10 @@
#include "../joinstate.h"
#include "../events/event.h"
+#include "util.h"
namespace QMatrixClient
{
- /**
- * @brief A crude wrapper around a container of pointers that owns pointers
- * to contained objects
- *
- * Similar to vector<unique_ptr<>>, upon deletion, EventsHolder
- * will delete all events contained in it.
- */
- template <typename ContainerT>
- class Owning : public ContainerT
- {
- public:
- Owning() = default;
-#if defined(_MSC_VER) && _MSC_VER < 1900
- // Workaround: Dangerous (auto_ptr style) copy constructor because
- // VS2013 (unnecessarily) instantiates EventList::QVector<>::toList()
- // which instantiates QList< Owning<> > which needs the contained
- // object to have a non-deleted copy constructor.
- Owning(Owning& other) : ContainerT(std::move(other)) { }
-#else
- Owning(Owning&) = delete;
-#endif
- Owning(Owning&& other) : ContainerT(std::move(other)) { }
- ~Owning() { for (auto e: *this) delete e; }
-
- /**
- * @brief returns the underlying events and releases the ownership
- *
- * Acts similar to unique_ptr::release.
- */
- ContainerT release() { return std::move(*this); }
- };
-
class SyncRoomData
{
public:
diff --git a/logging_util.h b/logging_util.h
deleted file mode 100644
index 47b2e062..00000000
--- a/logging_util.h
+++ /dev/null
@@ -1,63 +0,0 @@
-/******************************************************************************
- * Copyright (C) 2016 Kitsune Ral <kitsune-ral@users.sf.net>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-/**
- * @file logging_util.h - a collection of utilities to facilitate debug logging.
- */
-
-#pragma once
-
-#include <QtCore/QDebug>
-
-namespace QMatrixClient {
-
-// QDebug manipulators
-
-using QDebugManip = QDebug (*)(QDebug);
-
-/**
- * @brief QDebug manipulator to setup the stream for JSON output.
- *
- * Originally made to encapsulate the change in QDebug behavior in Qt 5.4
- * and the respective addition of QDebug::noquote().
- * Together with the operator<<() helper, the proposed usage is
- * (similar to std:: I/O manipulators):
- *
- * @example qDebug() << formatJson << json_object; // (QJsonObject, or QJsonValue, etc.)
- */
-static QDebugManip formatJson = [](QDebug debug_object) {
-#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0)
- return debug_object;
-#else
- return debug_object.noquote();
-#endif
- };
-
-/**
- * @brief A helper operator to facilitate using formatJson (and possibly other manipulators)
- *
- * @param debug_object to output the json to
- * @param qdm a QDebug manipulator
- * @return a copy of debug_object that has its mode altered by qdm
- */
-inline QDebug operator<< (QDebug debug_object, QDebugManip qdm) {
- return qdm(debug_object);
-}
-
-}
-
diff --git a/room.cpp b/room.cpp
index e9ca10c5..2378ecf4 100644
--- a/room.cpp
+++ b/room.cpp
@@ -96,15 +96,25 @@ class Room::Private
void appendEvent(Event* e)
{
- timeline.push_back(e);
- eventsIndex.insert(e->id(), timeline.crbegin());
- Q_ASSERT((*eventsIndex.value(e->id()))->id() == e->id());
+ insertEvent(e, timeline.end());
}
void prependEvent(Event* e)
{
- timeline.push_front(e);
- eventsIndex.insert(e->id(), prev(timeline.crend()));
- Q_ASSERT((*eventsIndex.value(e->id()))->id() == e->id());
+ insertEvent(e, timeline.begin());
+ }
+
+ /**
+ * Removes events from the passed container that are already in the timeline
+ */
+ void dropDuplicateEvents(Events& events) const
+ {
+ // Collect all duplicate events at the end of the container
+ auto dupsBegin =
+ std::stable_partition(events.begin(), events.end(),
+ [&] (Event* e) { return !eventsIndex.contains(e->id()); });
+ // Dispose of those dups
+ std::for_each(dupsBegin, events.end(), [] (Event* e) { delete e; });
+ events.erase(dupsBegin, events.end());
}
rev_iter_t readMarker(const User* user) const
@@ -121,6 +131,8 @@ class Room::Private
void insertMemberIntoMap(User* u);
void removeMemberFromMap(QString username, User* u);
+
+ void insertEvent(Event* e, Timeline::iterator where);
};
Room::Room(Connection* connection, QString id)
@@ -199,23 +211,29 @@ void Room::setLastReadEvent(User* user, Event* event)
std::pair<Room::Private::rev_iter_t, Room::Private::rev_iter_t>
Room::Private::promoteReadMarker(User* u, QString eventId)
{
- // If the eventId is empty, promote to the latest event; otherwise find the
- // event by its id and position the marker at it. If the event is not found
- // (most likely, because it's not fetched from the server), or it's older
- // than the current marker position, keep the marker intact.
- auto newMarker = eventId.isEmpty() ? timeline.crbegin() :
- eventsIndex.value(eventId, timeline.crend());
- auto prevMarker = readMarker(u);
- if (prevMarker <= newMarker)
+ Q_ASSERT_X (!eventId.isEmpty(), __FUNCTION__,
+ "Attempt to promote a read marker to an event with no id");
+
+ // Find the event by its id and position the marker at it. If the event
+ // is not found (most likely, because it's not fetched from the server),
+ // or if it's older than the current marker position, keep the marker intact.
+ const auto newMarker = eventsIndex.value(eventId, timeline.crend());
+ const auto prevMarker = readMarker(u);
+ if (prevMarker <= newMarker) // Remember, we deal with reverse iterators
return { prevMarker, prevMarker };
using namespace std;
- // Try to auto-promote the read marker over the user's own messages.
+ // Try to auto-promote the read marker over the user's own messages
+ // (switch to direct iterators for that).
auto eagerMarker = find_if(newMarker.base(), timeline.cend(),
[=](Event* e) { return e->senderId() != u->id(); });
if (eagerMarker > timeline.begin())
+ {
+ qDebug() << "Promoting the read marker in room" << displayname
+ << "for" << u->id() << "to" << (*(eagerMarker - 1))->id();
q->setLastReadEvent(u, *(eagerMarker - 1));
+ }
if (u == connection->user() && unreadMessages)
{
@@ -226,12 +244,13 @@ Room::Private::promoteReadMarker(User* u, QString eventId)
if (stillUnreadMessagesCount == 0)
{
unreadMessages = false;
- qDebug() << "Room" << q->displayName() << ": no more unread messages";
+ qDebug() << "Room" << q->displayName()
+ << "has no more unread messages";
emit q->unreadMessagesChanged(q);
}
else
- qDebug() << "Room" << q->displayName()
- << ": still" << stillUnreadMessagesCount << "unread message(s)";
+ qDebug() << "Room" << q->displayName() << "still has"
+ << stillUnreadMessagesCount << "unread message(s)";
}
// Return newMarker, rather than eagerMarker, to save markMessagesAsRead()
@@ -332,6 +351,26 @@ void Room::Private::removeMemberFromMap(QString username, User* u)
updateDisplayname();
}
+inline QByteArray makeErrorStr(Event* e, const char* msg)
+{
+ return QString("%1; event dump follows:\n%2")
+ .arg(msg, e->originalJson()).toUtf8();
+}
+
+void Room::Private::insertEvent(Event* e, Room::Timeline::iterator where)
+{
+ Q_ASSERT_X(e, __FUNCTION__, "Attempt to add nullptr to timeline");
+ Q_ASSERT_X(!e->id().isEmpty(), __FUNCTION__,
+ makeErrorStr(e,
+ "Event with empty id cannot be in the timeline"));
+ Q_ASSERT_X(!eventsIndex.contains(e->id()), __FUNCTION__,
+ makeErrorStr(e,
+ "Event is already in the timeline (use dropDuplicateEvents())"));
+ Q_ASSERT(where >= timeline.begin() && where <= timeline.end());
+ eventsIndex.insert(e->id(), rev_iter_t(timeline.insert(where, e) + 1));
+ Q_ASSERT((*eventsIndex.value(e->id()))->id() == e->id());
+}
+
void Room::Private::addMember(User *u)
{
if (!hasMember(u))
@@ -464,7 +503,7 @@ void Room::Private::getPreviousContent()
connect( roomMessagesJob, &RoomMessagesJob::result, [=]() {
if( !roomMessagesJob->error() )
{
- q->addHistoricalMessageEvents(roomMessagesJob->events());
+ q->addHistoricalMessageEvents(roomMessagesJob->releaseEvents());
prevBatch = roomMessagesJob->end();
}
roomMessagesJob = nullptr;
@@ -477,8 +516,9 @@ Connection* Room::connection() const
return d->connection;
}
-void Room::addNewMessageEvents(const Events& events)
+void Room::addNewMessageEvents(Events events)
{
+ d->dropDuplicateEvents(events);
if (events.empty())
return;
emit aboutToAddNewMessages(events);
@@ -494,11 +534,12 @@ bool Room::Private::isEventNotable(const Event* e) const
void Room::doAddNewMessageEvents(const Events& events)
{
-
+ Q_ASSERT(!events.isEmpty());
Timeline::size_type newUnreadMessages = 0;
- // The first message in the batch defines whose read marker we can
- // automatically promote any further. Others will need explicit read receipts
+ // The first event in the batch defines whose read marker we can
+ // automatically promote any further (if this author has read the whole
+ // timeline by then). Others will need explicit read receipts
// from the server (or, for the local user, markMessagesAsRead() invocation)
// to promote their read markers over the new message events.
User* firstWriter = connection()->user(events.front()->senderId());
@@ -509,19 +550,26 @@ void Room::doAddNewMessageEvents(const Events& events)
d->appendEvent(e);
newUnreadMessages += d->isEventNotable(e);
}
+ qDebug() << "Added" << events.size()
+ << "(with" << newUnreadMessages << "notable)"
+ << "new events to room" << displayName();
if (canAutoPromote)
+ {
+ qDebug() << "Auto-promoting" << firstWriter->id() << "over own events";
d->promoteReadMarker(firstWriter, events.front()->id());
+ }
if( !d->unreadMessages && newUnreadMessages > 0)
{
d->unreadMessages = true;
emit unreadMessagesChanged(this);
- qDebug() << "Room" << displayName() << ": unread messages";
+ qDebug() << "Room" << displayName() << "has unread messages";
}
}
-void Room::addHistoricalMessageEvents(const Events& events)
+void Room::addHistoricalMessageEvents(Events events)
{
+ d->dropDuplicateEvents(events);
if (events.empty())
return;
emit aboutToAddHistoricalMessages(events);
@@ -531,9 +579,12 @@ void Room::addHistoricalMessageEvents(const Events& events)
void Room::doAddHistoricalMessageEvents(const Events& events)
{
+ Q_ASSERT(!events.isEmpty());
// Historical messages arrive in newest-to-oldest order
for (auto e: events)
d->prependEvent(e);
+ qDebug() << "Added" << events.size() << "historical events to room"
+ << displayName();
}
void Room::processStateEvents(const Events& events)
diff --git a/room.h b/room.h
index ff958680..60b59bb3 100644
--- a/room.h
+++ b/room.h
@@ -79,10 +79,9 @@ namespace QMatrixClient
* Finds in the timeline and marks as read the event with
* the specified id; also posts a read receipt to the server either
* for this message or, if it's from the local user, for
- * the nearest non-local message before. If the event id is empty,
- * marks the whole timeline as read.
+ * the nearest non-local message before. uptoEventId must be non-empty.
*/
- Q_INVOKABLE void markMessagesAsRead(QString uptoEventId = {});
+ Q_INVOKABLE void markMessagesAsRead(QString uptoEventId);
Q_INVOKABLE bool hasUnreadMessages();
@@ -133,8 +132,8 @@ namespace QMatrixClient
class Private;
Private* d;
- void addNewMessageEvents(const Events& events);
- void addHistoricalMessageEvents(const Events& events);
+ void addNewMessageEvents(Events events);
+ void addHistoricalMessageEvents(Events events);
void setLastReadEvent(User* user, Event* event);
};
diff --git a/util.h b/util.h
new file mode 100644
index 00000000..bc16c413
--- /dev/null
+++ b/util.h
@@ -0,0 +1,169 @@
+/******************************************************************************
+ * Copyright (C) 2016 Kitsune Ral <kitsune-ral@users.sf.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file logging_util.h - a collection of utilities to facilitate debug logging.
+ */
+
+#pragma once
+
+#include <QtCore/QDebug>
+
+namespace QMatrixClient
+{
+
+ // QDebug manipulators
+
+ using QDebugManip = QDebug (*)(QDebug);
+
+ /**
+ * @brief QDebug manipulator to setup the stream for JSON output.
+ *
+ * Originally made to encapsulate the change in QDebug behavior in Qt 5.4
+ * and the respective addition of QDebug::noquote().
+ * Together with the operator<<() helper, the proposed usage is
+ * (similar to std:: I/O manipulators):
+ *
+ * @example qDebug() << formatJson << json_object; // (QJsonObject, etc.)
+ */
+ static QDebugManip formatJson = [](QDebug debug_object) {
+ #if QT_VERSION < QT_VERSION_CHECK(5, 4, 0)
+ return debug_object;
+ #else
+ return debug_object.noquote();
+ #endif
+ };
+
+ /**
+ * @brief A helper operator to facilitate usage of formatJson (and possibly
+ * other manipulators)
+ *
+ * @param debug_object to output the json to
+ * @param qdm a QDebug manipulator
+ * @return a copy of debug_object that has its mode altered by qdm
+ */
+ inline QDebug operator<< (QDebug debug_object, QDebugManip qdm)
+ {
+ return qdm(debug_object);
+ }
+
+ /**
+ * @brief A crude wrapper around a container of pointers that owns pointers
+ * to contained objects
+ *
+ * Similar to vector<unique_ptr<>>, upon deletion, this wrapper
+ * will delete all events contained in it. This wrapper can be used
+ * over Qt containers, which are incompatible with unique_ptr and even
+ * with QScopedPointer (which is the reason of its creation).
+ */
+ template <typename ContainerT>
+ class Owning : public ContainerT
+ {
+ public:
+ Owning() = default;
+#if defined(_MSC_VER) && _MSC_VER < 1900
+ // Workaround: Dangerous (auto_ptr style) copy constructor because
+ // in case of Owning< QVector<> > VS2013 (unnecessarily) instantiates
+ // QVector<>::toList() which instantiates QList< Owning<> > which
+ // requires the contained object to have a copy constructor.
+ Owning(Owning& other) : ContainerT(std::move(other)) { }
+#else
+ Owning(Owning&) = delete;
+#endif
+ Owning(Owning&& other) = default;
+ Owning& operator=(Owning&& other)
+ {
+ assign(other.release());
+ return *this;
+ }
+
+ ~Owning() { cleanup(); }
+
+ void assign(ContainerT&& other)
+ {
+ if (&other == this)
+ return;
+ cleanup();
+ ContainerT::operator=(other);
+ }
+
+ /**
+ * @brief returns the underlying container and releases the ownership
+ *
+ * Acts similar to unique_ptr::release.
+ */
+ ContainerT release()
+ {
+ ContainerT c;
+ ContainerT::swap(c);
+ return c;
+ }
+ private:
+ void cleanup() { for (auto e: *this) delete e; }
+ };
+
+ /**
+ * @brief Lookup a value by a key in a varargs list
+ *
+ * This function template takes the value of its first argument (selector)
+ * as a key and searches for it in the key-value map passed in a varargs list
+ * (every next pair of arguments forms a key-value pair). If a match is found,
+ * the respective value is returned; if no pairs matched, the last value
+ * (fallback) is returned.
+ *
+ * All options should be of the same type or implicitly castable to the
+ * type of the first option. Note that pointers to methods of different
+ * classes are of different object types, in particular.
+ *
+ * Below is an example of usage to select a parser depending on contents of
+ * a JSON object:
+ * {@code
+ * auto parser = lookup(obj.value["type"].toString(),
+ * "type1", fn1,
+ * "type2", fn2,
+ * fallbackFn);
+ * parser(obj);
+ * }
+ *
+ * The implementation is based on tail recursion; every recursion step
+ * removes 2 arguments (match and option). There's no selector value for the
+ * fallback option (the last one); therefore, the total number of lookup()
+ * arguments should be even: selector + n key-value pairs + fallback
+ *
+ * @note Beware of calling lookup() with a <code>const char*</code> selector
+ * (the first parameter) - most likely it won't do what you expect because
+ * of shallow comparison.
+ */
+ template <typename ValueT, typename SelectorT, typename KeyT, typename... Ts>
+ ValueT lookup(SelectorT selector, KeyT key, ValueT value, Ts... remainingMapping)
+ {
+ if( selector == key )
+ return value;
+
+ // Drop the failed key-value pair and recurse with 2 arguments less.
+ return lookup(selector, remainingMapping...);
+ }
+
+ template <typename SelectorT, typename ValueT>
+ ValueT lookup(SelectorT/*unused*/, ValueT fallback)
+ {
+ return fallback;
+ }
+
+}
+