TaskTree: Unify static assertions for all handlers

Introduce GroupItem::isInvocable() helper.
Add more compile tests.

Task-number: QTCREATORBUG-29834
Change-Id: I444efeda77d1fa584567403224595b821f2a2d43
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Jarek Kobus
2023-11-04 17:52:18 +01:00
parent 4c38f68d0f
commit 8fe1899c25
2 changed files with 150 additions and 95 deletions

View File

@@ -214,6 +214,17 @@ protected:
static GroupItem withTimeout(const GroupItem &item, std::chrono::milliseconds timeout, static GroupItem withTimeout(const GroupItem &item, std::chrono::milliseconds timeout,
const std::function<void()> &handler = {}); const std::function<void()> &handler = {});
// Checks if Function may be invoked with Args and if Function's return type is Result.
template <typename Result, typename Function, typename ...Args,
typename DecayedFunction = std::decay_t<Function>>
static constexpr bool isInvocable()
{
// Note, that std::is_invocable_r_v doesn't check Result type properly.
if constexpr (std::is_invocable_r_v<Result, DecayedFunction, Args...>)
return std::is_same_v<Result, std::invoke_result_t<DecayedFunction, Args...>>;
return false;
}
private: private:
Type m_type = Type::Group; Type m_type = Type::Group;
QList<GroupItem> m_children; QList<GroupItem> m_children;
@@ -254,40 +265,33 @@ public:
} }
private: private:
template<typename Handler> template <typename Handler>
static GroupSetupHandler wrapGroupSetup(Handler &&handler) static GroupSetupHandler wrapGroupSetup(Handler &&handler)
{ {
static constexpr bool isDynamic // S, V stands for: [S]etupResult, [V]oid
= std::is_same_v<SetupResult, std::invoke_result_t<std::decay_t<Handler>>>; static constexpr bool isS = isInvocable<SetupResult, Handler>();
constexpr bool isVoid static constexpr bool isV = isInvocable<void, Handler>();
= std::is_same_v<void, std::invoke_result_t<std::decay_t<Handler>>>; static_assert(isS || isV,
static_assert(isDynamic || isVoid, "Group setup handler needs to take no arguments and has to return void or SetupResult. "
"Group setup handler needs to take no arguments and has to return " "The passed handler doesn't fulfill these requirements.");
"void or SetupResult. The passed handler doesn't fulfill these requirements.");
return [=] { return [=] {
if constexpr (isDynamic) if constexpr (isS)
return std::invoke(handler); return std::invoke(handler);
std::invoke(handler); std::invoke(handler);
return SetupResult::Continue; return SetupResult::Continue;
}; };
}; };
template<typename Handler> template <typename Handler>
static GroupDoneHandler wrapGroupDone(Handler &&handler) static GroupDoneHandler wrapGroupDone(Handler &&handler)
{ {
static constexpr bool isBD // stands for [B]ool, [D]oneWith // B, V, D stands for: [B]ool, [V]oid, [D]oneWith
= std::is_invocable_r_v<bool, std::decay_t<Handler>, DoneWith>; static constexpr bool isBD = isInvocable<bool, Handler, DoneWith>();
static constexpr bool isB static constexpr bool isB = isInvocable<bool, Handler>();
= std::is_invocable_r_v<bool, std::decay_t<Handler>>; static constexpr bool isVD = isInvocable<void, Handler, DoneWith>();
static constexpr bool isVD // stands for [V]oid, [D]oneWith static constexpr bool isV = isInvocable<void, Handler>();
= std::is_invocable_r_v<void, std::decay_t<Handler>, DoneWith>; static_assert(isBD || isB || isVD || isV,
static constexpr bool isV "Group done handler needs to take (DoneWith) or (void) as an argument and has to "
= std::is_invocable_r_v<void, std::decay_t<Handler>>; "return void or bool. The passed handler doesn't fulfill these requirements.");
static constexpr bool isInvocable = isBD || isB || isVD || isV;
static_assert(isInvocable,
"Group done handler needs to take (DoneWith) or (void) "
"as arguments and has to return void or bool. "
"The passed handler doesn't fulfill these requirements.");
return [=](DoneWith result) { return [=](DoneWith result) {
if constexpr (isBD) if constexpr (isBD)
return std::invoke(handler, result); return std::invoke(handler, result);
@@ -338,24 +342,28 @@ public:
class TASKING_EXPORT Sync final : public GroupItem class TASKING_EXPORT Sync final : public GroupItem
{ {
public: public:
template<typename Function> template <typename Handler>
Sync(Function &&function) { addChildren({init(std::forward<Function>(function))}); } Sync(Handler &&handler) {
addChildren({ onGroupSetup(wrapHandler(std::forward<Handler>(handler))) });
}
private: private:
template<typename Function> template <typename Handler>
static GroupItem init(Function &&function) { static GroupSetupHandler wrapHandler(Handler &&handler) {
constexpr bool isInvocable = std::is_invocable_v<std::decay_t<Function>>; // B, V stands for: [B]ool, [V]oid
static_assert(isInvocable, static constexpr bool isB = isInvocable<bool, Handler>();
"Sync element: The synchronous function can't take any arguments."); static constexpr bool isV = isInvocable<void, Handler>();
constexpr bool isBool = std::is_same_v<bool, std::invoke_result_t<std::decay_t<Function>>>; static_assert(isB || isV,
constexpr bool isVoid = std::is_same_v<void, std::invoke_result_t<std::decay_t<Function>>>; "Sync handler needs to take no arguments and has to return void or bool. "
static_assert(isBool || isVoid, "The passed handler doesn't fulfill these requirements.");
"Sync element: The synchronous function has to return void or bool."); return [=] {
if constexpr (isBool) { if constexpr (isB) {
return onGroupSetup([function] { return function() ? SetupResult::StopWithSuccess return std::invoke(handler) ? SetupResult::StopWithSuccess
: SetupResult::StopWithError; }); : SetupResult::StopWithError;
} }
return onGroupSetup([function] { function(); return SetupResult::StopWithSuccess; }); std::invoke(handler);
return SetupResult::StopWithSuccess;
};
}; };
}; };
@@ -401,49 +409,39 @@ public:
} }
private: private:
template<typename Handler> template <typename Handler>
static GroupItem::TaskSetupHandler wrapSetup(Handler &&handler) { static GroupItem::TaskSetupHandler wrapSetup(Handler &&handler) {
if constexpr (std::is_same_v<Handler, SetupFunction>) if constexpr (std::is_same_v<Handler, SetupFunction>)
return {}; // When user passed {} for the setup handler. return {}; // When user passed {} for the setup handler.
static constexpr bool isDynamic // S, V stands for: [S]etupResult, [V]oid
= std::is_same_v<SetupResult, std::invoke_result_t<std::decay_t<Handler>, Task &>>; static constexpr bool isS = isInvocable<SetupResult, Handler, Task &>();
constexpr bool isVoid static constexpr bool isV = isInvocable<void, Handler, Task &>();
= std::is_same_v<void, std::invoke_result_t<std::decay_t<Handler>, Task &>>; static_assert(isS || isV,
static_assert(isDynamic || isVoid, "Task setup handler needs to take (Task &) as an argument and has to return void or "
"Task setup handler needs to take (Task &) as an argument and has to return " "SetupResult. The passed handler doesn't fulfill these requirements.");
"void or SetupResult. The passed handler doesn't fulfill these requirements.");
return [=](TaskInterface &taskInterface) { return [=](TaskInterface &taskInterface) {
Adapter &adapter = static_cast<Adapter &>(taskInterface); Adapter &adapter = static_cast<Adapter &>(taskInterface);
if constexpr (isDynamic) if constexpr (isS)
return std::invoke(handler, *adapter.task()); return std::invoke(handler, *adapter.task());
std::invoke(handler, *adapter.task()); std::invoke(handler, *adapter.task());
return SetupResult::Continue; return SetupResult::Continue;
}; };
}; };
template<typename Handler> template <typename Handler>
static GroupItem::TaskDoneHandler wrapDone(Handler &&handler) { static GroupItem::TaskDoneHandler wrapDone(Handler &&handler) {
if constexpr (std::is_same_v<Handler, DoneFunction>) if constexpr (std::is_same_v<Handler, DoneFunction>)
return {}; // When user passed {} for the done handler. return {}; // When user passed {} for the done handler.
static constexpr bool isBTD // stands for [B]ool, [T]ask, [D]oneWith // B, V, T, D stands for: [B]ool, [V]oid, [T]ask, [D]oneWith
= std::is_invocable_r_v<bool, std::decay_t<Handler>, const Task &, DoneWith>; static constexpr bool isBTD = isInvocable<bool, Handler, const Task &, DoneWith>();
static constexpr bool isBT static constexpr bool isBT = isInvocable<bool, Handler, const Task &>();
= std::is_invocable_r_v<bool, std::decay_t<Handler>, const Task &>; static constexpr bool isBD = isInvocable<bool, Handler, DoneWith>();
static constexpr bool isBD static constexpr bool isB = isInvocable<bool, Handler>();
= std::is_invocable_r_v<bool, std::decay_t<Handler>, DoneWith>; static constexpr bool isVTD = isInvocable<void, Handler, const Task &, DoneWith>();
static constexpr bool isB static constexpr bool isVT = isInvocable<void, Handler, const Task &>();
= std::is_invocable_r_v<bool, std::decay_t<Handler>>; static constexpr bool isVD = isInvocable<void, Handler, DoneWith>();
static constexpr bool isVTD // stands for [V]oid, [T]ask, [D]oneWith static constexpr bool isV = isInvocable<void, Handler>();
= std::is_invocable_r_v<void, std::decay_t<Handler>, const Task &, DoneWith>; static_assert(isBTD || isBT || isBD || isB || isVTD || isVT || isVD || isV,
static constexpr bool isVT
= std::is_invocable_r_v<void, std::decay_t<Handler>, const Task &>;
static constexpr bool isVD
= std::is_invocable_r_v<void, std::decay_t<Handler>, DoneWith>;
static constexpr bool isV
= std::is_invocable_r_v<void, std::decay_t<Handler>>;
static constexpr bool isInvocable = isBTD || isBT || isBD || isB
|| isVTD || isVT || isVD || isV;
static_assert(isInvocable,
"Task done handler needs to take (const Task &, DoneWith), (const Task &), " "Task done handler needs to take (const Task &, DoneWith), (const Task &), "
"(DoneWith) or (void) as arguments and has to return void or bool. " "(DoneWith) or (void) as arguments and has to return void or bool. "
"The passed handler doesn't fulfill these requirements."); "The passed handler doesn't fulfill these requirements.");
@@ -503,23 +501,19 @@ public:
template <typename StorageStruct, typename StorageHandler> template <typename StorageStruct, typename StorageHandler>
void onStorageSetup(const TreeStorage<StorageStruct> &storage, StorageHandler &&handler) { void onStorageSetup(const TreeStorage<StorageStruct> &storage, StorageHandler &&handler) {
constexpr bool isInvocable = std::is_invocable_v<std::decay_t<StorageHandler>, static_assert(std::is_invocable_v<std::decay_t<StorageHandler>, StorageStruct &>,
StorageStruct &>;
static_assert(isInvocable,
"Storage setup handler needs to take (Storage &) as an argument. " "Storage setup handler needs to take (Storage &) as an argument. "
"The passed handler doesn't fulfill these requirements."); "The passed handler doesn't fulfill this requirement.");
setupStorageHandler(storage, setupStorageHandler(storage,
wrapHandler<StorageStruct>(std::forward<StorageHandler>(handler)), {}); wrapHandler<StorageStruct>(std::forward<StorageHandler>(handler)), {});
} }
template <typename StorageStruct, typename StorageHandler> template <typename StorageStruct, typename StorageHandler>
void onStorageDone(const TreeStorage<StorageStruct> &storage, StorageHandler &&handler) { void onStorageDone(const TreeStorage<StorageStruct> &storage, StorageHandler &&handler) {
constexpr bool isInvocable = std::is_invocable_v<std::decay_t<StorageHandler>, static_assert(std::is_invocable_v<std::decay_t<StorageHandler>, const StorageStruct &>,
const StorageStruct &>;
static_assert(isInvocable,
"Storage done handler needs to take (const Storage &) as an argument. " "Storage done handler needs to take (const Storage &) as an argument. "
"The passed handler doesn't fulfill these requirements."); "The passed handler doesn't fulfill this requirement.");
setupStorageHandler(storage, setupStorageHandler(storage, {},
{}, wrapHandler<StorageStruct>(std::forward<StorageHandler>(handler))); wrapHandler<const StorageStruct>(std::forward<StorageHandler>(handler)));
} }
signals: signals:
@@ -536,7 +530,7 @@ private:
template <typename StorageStruct, typename StorageHandler> template <typename StorageStruct, typename StorageHandler>
StorageVoidHandler wrapHandler(StorageHandler &&handler) { StorageVoidHandler wrapHandler(StorageHandler &&handler) {
return [=](void *voidStruct) { return [=](void *voidStruct) {
StorageStruct *storageStruct = static_cast<StorageStruct *>(voidStruct); auto *storageStruct = static_cast<StorageStruct *>(voidStruct);
std::invoke(handler, *storageStruct); std::invoke(handler, *storageStruct);
}; };
} }

View File

@@ -139,28 +139,89 @@ void tst_Tasking::validConstructs()
// When turning each of below blocks on, you should see the specific compiler error message. // When turning each of below blocks on, you should see the specific compiler error message.
// Sync handler needs to take no arguments and has to return void or bool.
#if 0 #if 0
{ Sync([] { return 7; });
// "Sync element: The synchronous function has to return void or bool." #endif
const auto setupSync = [] { return 3; }; #if 0
const Sync sync(setupSync); Sync([](int) { });
} #endif
#if 0
Sync([](int) { return true; });
#endif #endif
// Group setup handler needs to take no arguments and has to return void or SetupResult.
#if 0 #if 0
{ onGroupSetup([] { return 7; });
// "Sync element: The synchronous function can't take any arguments." #endif
const auto setupSync = [](int) { }; #if 0
const Sync sync(setupSync); onGroupSetup([](int) { });
}
#endif #endif
// Group done handler needs to take (DoneWith) or (void) as an argument and has to
// return void or bool.
#if 0 #if 0
{ onGroupDone([] { return 7; });
// "Sync element: The synchronous function can't take any arguments." #endif
const auto setupSync = [](int) { return true; }; #if 0
const Sync sync(setupSync); onGroupDone([](DoneWith) { return 7; });
} #endif
#if 0
onGroupDone([](int) { });
#endif
#if 0
onGroupDone([](DoneWith, int) { });
#endif
// Task setup handler needs to take (Task &) as an argument and has to return void or
// SetupResult.
#if 0
TestTask([] {});
#endif
#if 0
TestTask([] { return 7; });
#endif
#if 0
TestTask([](TaskObject &) { return 7; });
#endif
#if 0
TestTask([](TaskObject &, int) { return SetupResult::Continue; });
#endif
// Task done handler needs to take (const Task &, DoneWith), (const Task &),
// (DoneWith) or (void) as arguments and has to return void or bool.
#if 0
TestTask({}, [](const TaskObject &, DoneWith) { return 7; });
#endif
#if 0
TestTask({}, [](const TaskObject &) { return 7; });
#endif
#if 0
TestTask({}, [] { return 7; });
#endif
#if 0
TestTask({}, [](const TaskObject &, DoneWith, int) {});
#endif
#if 0
TestTask({}, [](const TaskObject &, int) {});
#endif
#if 0
TestTask({}, [](DoneWith, int) {});
#endif
#if 0
TestTask({}, [](int) {});
#endif
#if 0
TestTask({}, [](const TaskObject &, DoneWith, int) { return true; });
#endif
#if 0
TestTask({}, [](const TaskObject &, int) { return true; });
#endif
#if 0
TestTask({}, [](DoneWith, int) { return true; });
#endif
#if 0
TestTask({}, [](int) { return true; });
#endif #endif
} }