cmake: avoid de-duplication of user's CXXFLAGS
[quassel.git] / src / common / funchelpers.h
1 /***************************************************************************
2  *   Copyright (C) 2005-2019 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #pragma once
22
23 #include <array>
24 #include <functional>
25 #include <tuple>
26 #include <type_traits>
27 #include <utility>
28
29 #include <boost/optional.hpp>
30
31 #include <QDebug>
32 #include <QVariant>
33 #include <QVariantList>
34
35 // ---- Function traits --------------------------------------------------------------------------------------------------------------------
36
37 namespace detail {
38
39 /// @cond DOXYGEN_CANNOT_PARSE_THIS
40
41 // Primary template
42 template<typename Func>
43 struct FuncHelper : public FuncHelper<decltype(&Func::operator())>
44 {};
45
46 // Overload for free function
47 template<typename R, typename... Args>
48 struct FuncHelper<R(*)(Args...)>
49 {
50     using FunctionType = std::function<R(Args...)>;
51     using ReturnType = R;
52     using ArgsTuple = std::tuple<Args...>;
53 };
54
55 // Overload for member function with non-const call operator
56 template<typename C, typename R, typename... Args>
57 struct FuncHelper<R(C::*)(Args...)> : public FuncHelper<R(*)(Args...)>
58 {
59     using ClassType = C;
60 };
61
62 // Overload for member function with const call operator
63 template<typename C, typename R, typename... Args>
64 struct FuncHelper<R(C::*)(Args...) const> : public FuncHelper<R(C::*)(Args...)>
65 {};
66
67 /// @endcond
68
69 }  // namespace detail
70
71 /**
72  * Provides traits for the given callable.
73  */
74 template<typename Callable>
75 using FunctionTraits = detail::FuncHelper<Callable>;
76
77 // ---- Invoke function with argument list -------------------------------------------------------------------------------------------------
78
79 namespace detail {
80
81 // Helper for invoking the callable, wrapping its return value in a QVariant (default-constructed if callable returns void).
82 // The correct overload is selected via SFINAE.
83 template<typename Callable, typename ...Args>
84 auto invokeWithArgs(const Callable& c, Args&&... args)
85     -> std::enable_if_t<std::is_void<typename FunctionTraits<Callable>::ReturnType>::value, QVariant>
86 {
87     c(std::forward<Args>(args)...);
88     return QVariant{};
89 }
90
91 template<typename Callable, typename ...Args>
92 auto invokeWithArgs(const Callable& c, Args&&... args)
93     -> std::enable_if_t<!std::is_void<typename FunctionTraits<Callable>::ReturnType>::value, QVariant>
94 {
95     return QVariant::fromValue(c(std::forward<Args>(args)...));
96 }
97
98 // Helper for unpacking the argument list via an index sequence
99 template<typename Callable, std::size_t ...Is, typename ArgsTuple = typename FunctionTraits<Callable>::ArgsTuple>
100 boost::optional<QVariant> invokeWithArgsList(const Callable& c, const QVariantList& args, std::index_sequence<Is...>)
101 {
102     // Sanity check that all types can be converted
103     std::array<bool, std::tuple_size<ArgsTuple>::value> convertible{{args[Is].canConvert<std::decay_t<std::tuple_element_t<Is, ArgsTuple>>>()...}};
104     for (size_t i = 0; i < convertible.size(); ++i) {
105         if (!convertible[i]) {
106             qWarning() << "Cannot convert parameter" << i << "from type" << args[static_cast<int>(i)].typeName() << "to expected argument type";
107             return boost::none;
108         }
109     }
110
111     // Invoke callable with unmarshalled arguments
112     return invokeWithArgs(c, args[Is].value<std::decay_t<std::tuple_element_t<Is, ArgsTuple>>>()...);
113 }
114
115 }  // detail
116
117 /**
118  * Invokes the given callable with the arguments contained in the given variant list.
119  *
120  * The types contained in the given QVariantList are converted to the types expected by the callable.
121  * If the invocation is successful, the returned optional contains a QVariant with the return value,
122  * or an invalid QVariant if the callable returns void.
123  * If the conversion fails, or if the argument count does not match, this function returns boost::none
124  * and the callable is not invoked.
125  *
126  * @param c    Callable
127  * @param args Arguments to be given to the callable
128  * @returns An optional containing a QVariant with the return value if the callable could be invoked with
129  *          the given list of arguments; otherwise boost::none
130  */
131 template<typename Callable>
132 boost::optional<QVariant> invokeWithArgsList(const Callable& c, const QVariantList& args)
133 {
134     using ArgsTuple = typename FunctionTraits<Callable>::ArgsTuple;
135     constexpr auto tupleSize = std::tuple_size<ArgsTuple>::value;
136
137     if (tupleSize != args.size()) {
138         qWarning().nospace() << "Argument count mismatch! Expected: " << tupleSize << ", actual: " << args.size();
139         return boost::none;
140     }
141     return detail::invokeWithArgsList(c, args, std::make_index_sequence<tupleSize>{});
142 }
143
144 /**
145  * Invokes the given member function pointer on the given object with the arguments contained in the given variant list.
146  *
147  * The types contained in the given QVariantList are converted to the types expected by the member function.
148  * If the invocation is successful, the returned optional contains a QVariant with the return value,
149  * or an invalid QVariant if the member function returns void.
150  * If the conversion fails, or if the argument count does not match, this function returns boost::none
151  * and the member function is not invoked.
152  *
153  * @param c    Callable
154  * @param args Arguments to be given to the member function
155  * @returns An optional containing a QVariant with the return value if the member function could be invoked with
156  *          the given list of arguments; otherwise boost::none
157  */
158 template<typename R, typename C, typename ...Args>
159 boost::optional<QVariant> invokeWithArgsList(C* object, R(C::*func)(Args...), const QVariantList& args)
160 {
161     if (sizeof...(Args) != args.size()) {
162         qWarning().nospace() << "Argument count mismatch! Expected: " << sizeof...(Args) << ", actual: " << args.size();
163         return boost::none;
164     }
165     return detail::invokeWithArgsList([object, func](Args&&... args) {
166         return (object->*func)(std::forward<decltype(args)>(args)...);
167     }, args, std::make_index_sequence<sizeof...(Args)>{});
168 }