ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
get_value.hpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2016-2022 The ESPResSo project
3 *
4 * This file is part of ESPResSo.
5 *
6 * ESPResSo is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * ESPResSo is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#ifndef SCRIPT_INTERFACE_GET_VALUE_HPP
21#define SCRIPT_INTERFACE_GET_VALUE_HPP
22
23#include "Exception.hpp"
24#include "ObjectHandle.hpp"
25#include "Variant.hpp"
26
27#include <utils/demangle.hpp>
28
29#include <boost/algorithm/string/join.hpp>
30#include <boost/range/algorithm/transform.hpp>
31
32#include <cstddef>
33#include <memory>
34#include <set>
35#include <stdexcept>
36#include <string>
37#include <type_traits>
38#include <utility>
39#include <vector>
40
41namespace ScriptInterface {
42namespace detail {
43
44/**
45 * @brief Convert a demangled symbol into a human-readable name that omits
46 * container allocators, key hashes and implementation-specific namespaces.
47 * When the data type involves the @ref Variant type, it is recursively
48 * replaced by the string "Variant{type1,type2,...}" based on the actual
49 * contents of the variant.
50 */
51namespace demangle {
52
53inline std::string simplify_symbol_variant(Variant const &v);
54
55/** @brief Simplify the demangled symbol of an object. */
56template <typename T> auto simplify_symbol(T const *) {
57 auto constexpr is_string = std::is_same_v<T, std::string>;
58 auto const symbol_for_variant = Utils::demangle<Variant>();
59 auto const name_for_variant = std::string("ScriptInterface::Variant");
60 auto name = (is_string) ? std::string{"std::string"} : Utils::demangle<T>();
61 for (std::string::size_type pos{};
62 (pos = name.find(symbol_for_variant, pos)) != name.npos;
63 pos += name_for_variant.length()) {
64 name.replace(pos, symbol_for_variant.length(), name_for_variant);
65 }
66 return name;
67}
68
69/** @overload */
70template <typename T, std::size_t N>
71auto simplify_symbol(Utils::Vector<T, N> const *) {
72 auto const name_val = simplify_symbol(static_cast<T *>(nullptr));
73 return "Utils::Vector<" + Utils::demangle<T>() + ", " + std::to_string(N) +
74 ">";
75}
76
77/** @overload */
78template <typename T> auto simplify_symbol(std::vector<T> const *vec) {
79 auto const name_val = simplify_symbol(static_cast<T *>(nullptr));
80 std::string metadata{""};
81 if (vec) {
82 metadata += "{.size=" + std::to_string(vec->size()) + "}";
83 }
84 return "std::vector<" + name_val + ">" + metadata;
85}
86
87/** @overload */
88inline auto simplify_symbol(std::vector<Variant> const *vec) {
89 auto value_type_name = std::string("ScriptInterface::Variant");
90 std::string metadata{""};
91 if (vec) {
92 std::set<std::string> types = {};
93 for (auto const &v : *vec) {
94 types.insert(simplify_symbol_variant(v));
95 }
96 value_type_name += "{" + boost::algorithm::join(types, ", ") + "}";
97 metadata += "{.size=" + std::to_string(vec->size()) + "}";
98 }
99 return "std::vector<" + value_type_name + ">" + metadata;
100}
101
102/** @overload */
103template <typename K, typename V>
104auto simplify_symbol(std::unordered_map<K, V> const *) {
105 auto const name_key = simplify_symbol(static_cast<K *>(nullptr));
106 auto const name_val = simplify_symbol(static_cast<V *>(nullptr));
107 return "std::unordered_map<" + name_key + ", " + name_val + ">";
108}
109
110/** @overload */
111template <typename K>
112auto simplify_symbol(std::unordered_map<K, Variant> const *map) {
113 auto const name_key = simplify_symbol(static_cast<K *>(nullptr));
114 auto value_type_name = std::string("ScriptInterface::Variant");
115 if (map) {
116 std::set<std::string> types = {};
117 for (auto const &kv : *map) {
118 types.insert(simplify_symbol_variant(kv.second));
119 }
120 value_type_name += "{" + boost::algorithm::join(types, ", ") + "}";
121 }
122 return "std::unordered_map<" + name_key + ", " + value_type_name + ">";
123}
124
125struct simplify_symbol_visitor : boost::static_visitor<std::string> {
126 template <class T> std::string operator()(T const &t) const {
127 return simplify_symbol(&t);
128 }
129};
130
131/** @brief Simplify the demangled symbol of an object wrapped in a variant. */
132inline std::string simplify_symbol_variant(Variant const &v) {
133 return boost::apply_visitor(simplify_symbol_visitor(), v);
134}
135
136/** @brief Simplify the demangled symbol of a container @c value_type. */
137template <typename T> auto simplify_symbol_containee(T const *) {
138 return std::string("");
139}
140
141/** @overload */
142template <typename T> auto simplify_symbol_containee(std::vector<T> const *) {
143 auto const name_val = simplify_symbol(static_cast<T *>(nullptr));
144 return name_val;
145}
146
147/** @overload */
148template <typename K, typename V>
149auto simplify_symbol_containee(std::unordered_map<K, V> const *) {
150 auto const name_key = simplify_symbol(static_cast<K *>(nullptr));
151 auto const name_val = simplify_symbol(static_cast<V *>(nullptr));
152 return name_key + "' or '" + name_val;
153}
154
155struct simplify_symbol_containee_visitor : boost::static_visitor<std::string> {
156 template <class T> std::string operator()(const T &) const {
157 return simplify_symbol_containee(static_cast<T *>(nullptr));
158 }
159};
160
161/**
162 * @brief Simplify the demangled symbol of a container @c value_type wrapped
163 * in a variant.
164 */
165inline auto simplify_symbol_containee_variant(Variant const &v) {
166 return boost::apply_visitor(simplify_symbol_containee_visitor(), v);
167}
168
169} // namespace demangle
170
171/*
172 * Allows
173 * T -> T,
174 * floating point -> floating point and
175 * integral -> floating point
176 */
177template <class To, class From>
178using allow_conversion =
179 std::integral_constant<bool, std::is_same_v<To, From> ||
180 (std::is_convertible_v<To, From> &&
181 std::is_floating_point_v<To> &&
182 std::is_arithmetic_v<From>)>;
183
184template <class To> struct conversion_visitor : boost::static_visitor<To> {
185 template <class From>
186 std::enable_if_t<allow_conversion<To, From>::value, To>
187 operator()(const From &value) const {
188 return To(value);
189 }
190
191 template <class From>
192 std::enable_if_t<!allow_conversion<To, From>::value, To>
193 operator()(const From &) const {
194 throw boost::bad_get{};
195 }
196};
197
198/**
199 * @brief Implementation of get_value.
200 *
201 * Helper struct is needed because partial specialization of functions
202 * is not allowed.
203 */
204template <typename T, typename = void> struct get_value_helper {
205 T operator()(Variant const &v) const {
206 return boost::apply_visitor(detail::conversion_visitor<T>(), v);
207 }
208};
209
210template <class T, std::size_t N>
211struct vector_conversion_visitor : boost::static_visitor<Utils::Vector<T, N>> {
212 Utils::Vector<T, N> operator()(Utils::Vector<T, N> const &v) const {
213 return v;
214 }
215
216 /* We try to unpack variant vectors and check if they
217 * are convertible element by element. */
218 auto operator()(std::vector<Variant> const &vv) const {
219 if (N != vv.size()) {
220 throw boost::bad_get{};
221 }
222
224 boost::transform(vv, ret.begin(),
225 [](const Variant &v) { return get_value_helper<T>{}(v); });
226
227 return ret;
228 }
229
230 template <typename U>
231 std::enable_if_t<allow_conversion<T, U>::value, Utils::Vector<T, N>>
232 operator()(std::vector<U, std::allocator<U>> const &v) const {
233 if (N != v.size()) {
234 throw boost::bad_get{};
235 }
236 return Utils::Vector<T, N>(v);
237 }
238
239 template <typename U> Utils::Vector<T, N> operator()(U const &) const {
240 throw boost::bad_get{};
241 }
242};
243
244/* Utils::Vector<T, N> case */
245template <typename T, std::size_t N>
246struct get_value_helper<Utils::Vector<T, N>> {
247 Utils::Vector<T, N> operator()(Variant const &v) const {
248 return boost::apply_visitor(detail::vector_conversion_visitor<T, N>(), v);
249 }
250};
251
252template <typename T>
253struct GetVectorOrEmpty : boost::static_visitor<std::vector<T>> {
254 /* Catch all case -> wrong type. */
255 template <typename U> std::vector<T> operator()(U const &) const {
256 throw boost::bad_get{};
257 }
258
259 /* Standard case, correct type */
260 std::vector<T> operator()(std::vector<T> const &v) const { return v; }
261
262 template <typename V = T,
263 std::enable_if_t<!std::is_same_v<V, Variant>, bool> = true>
264 std::vector<T> operator()(std::vector<Variant> const &vv) const {
265 std::vector<T> ret(vv.size());
266
267 boost::transform(vv, ret.begin(),
268 [](const Variant &v) { return get_value_helper<T>{}(v); });
269
270 return ret;
271 }
272};
273
274/* std::vector cases */
275template <typename T> struct get_value_helper<std::vector<T>, void> {
276 std::vector<T> operator()(Variant const &v) const {
277 return boost::apply_visitor(GetVectorOrEmpty<T>(), v);
278 }
279};
280
281template <typename K, typename T>
282struct GetMapOrEmpty : boost::static_visitor<std::unordered_map<K, T>> {
283 /* Catch all case -> wrong type. */
284 template <typename U> std::unordered_map<K, T> operator()(U const &) const {
285 throw boost::bad_get{};
286 }
287
288 /* Standard case, correct type */
289 std::unordered_map<K, T> operator()(std::unordered_map<K, T> const &v) const {
290 return v;
291 }
292
293 template <typename V = T,
294 std::enable_if_t<!std::is_same_v<V, Variant>, bool> = true>
295 std::unordered_map<K, T>
296 operator()(std::unordered_map<K, Variant> const &v) const {
297 std::unordered_map<K, T> ret;
298 for (auto it = v.begin(); it != v.end(); ++it) {
299 ret.insert({it->first, get_value_helper<T>{}(it->second)});
300 }
301 return ret;
302 }
303};
304
305/* std::unordered_map cases */
306template <typename T>
307struct get_value_helper<std::unordered_map<int, T>, void> {
308 std::unordered_map<int, T> operator()(Variant const &v) const {
309 return boost::apply_visitor(GetMapOrEmpty<int, T>(), v);
310 }
311};
312template <typename T>
313struct get_value_helper<std::unordered_map<std::string, T>, void> {
314 std::unordered_map<std::string, T> operator()(Variant const &v) const {
315 return boost::apply_visitor(GetMapOrEmpty<std::string, T>(), v);
316 }
317};
318
319/** Custom error for a conversion that fails when the value is a nullptr. */
320class bad_get_nullptr : public boost::bad_get {};
321
322/* This allows direct retrieval of a shared_ptr to the object from
323 * an ObjectRef variant. If the type is a derived type, the type is
324 * also checked.
325 */
326template <typename T>
327struct get_value_helper<
328 std::shared_ptr<T>,
329 typename std::enable_if_t<std::is_base_of_v<ObjectHandle, T>, void>> {
330 std::shared_ptr<T> operator()(Variant const &v) const {
331 auto so_ptr = boost::get<ObjectRef>(v);
332 if (!so_ptr) {
333 throw bad_get_nullptr{};
334 }
335
336 auto t_ptr = std::dynamic_pointer_cast<T>(so_ptr);
337
338 if (t_ptr) {
339 return t_ptr;
340 }
341
342 throw boost::bad_get{};
343 }
344};
345
346/**
347 * @brief Re-throw a @c boost::bad_get exception wrapped in an @ref Exception.
348 * Write a custom error message for invalid conversions due to type mismatch
349 * and due to nullptr values, possibly with context information if the variant
350 * is a container.
351 * @tparam T Which type the variant was supposed to convert to
352 */
353template <typename T>
354inline void handle_bad_get(Variant const &v, std::string const &name) {
355 auto const container_name = demangle::simplify_symbol_variant(v);
356 auto const containee_name = demangle::simplify_symbol_containee_variant(v);
357 auto const expected_containee_name =
358 demangle::simplify_symbol_containee(static_cast<T *>(nullptr));
359 auto const from_container = !containee_name.empty();
360 auto const to_container = !expected_containee_name.empty();
361 auto what = "Provided argument of type '" + container_name + "'";
362 if (not name.empty()) {
363 what += " for parameter '" + name + "'";
364 }
365 try {
366 throw;
367 } catch (bad_get_nullptr const &) {
368 auto const item_error = (to_container) ? " contains a value that" : "";
369 throw Exception(what + item_error + " is a null pointer");
370 } catch (boost::bad_get const &) {
371 auto const non_convertible = std::string(" is not convertible to ");
372 auto item_error = std::string("");
373 if (from_container and to_container) {
374 item_error += " because it contains a value that";
375 item_error += non_convertible + "'" + expected_containee_name + "'";
376 }
377 auto const target = demangle::simplify_symbol(static_cast<T *>(nullptr));
378 throw Exception(what + non_convertible + "'" + target + "'" + item_error);
379 }
380}
381
382template <typename T> T get_value(Variant const &v, std::string const &name) {
383 try {
384 return detail::get_value_helper<T>{}(v);
385 } catch (...) {
386 detail::handle_bad_get<T>(v, name);
387 throw;
388 }
389}
390
391} // namespace detail
392
393/**
394 * @brief Extract value of specific type T from a Variant.
395 *
396 * This is a wrapper around boost::get that allows us to
397 * customize the behavior for different types. This is
398 * needed e.g. to deal with containers whose elements
399 * have mixed types that are implicitly convertible
400 * to a requested type.
401 */
402template <typename T> T get_value(Variant const &v) {
403 return detail::get_value<T>(v, "");
404}
405
406/**
407 * @brief Get a value from a VariantMap by name, or throw
408 * if it does not exist or is not convertible to
409 * the target type.
410 */
411template <typename T>
412T get_value(VariantMap const &vals, std::string const &name) {
413 if (vals.count(name) == 0ul) {
414 throw Exception("Parameter '" + name + "' is missing.");
415 }
416 return detail::get_value<T>(vals.at(name), name);
417}
418
419/**
420 * @brief Get a value from a VariantMap by name, or return a default
421 * value if it does not exist.
422 */
423template <typename T>
424T get_value_or(VariantMap const &vals, std::string const &name,
425 T const &default_) {
426 if (vals.count(name)) {
427 return get_value<T>(vals.at(name));
428 }
429 return default_;
430}
431
432/**
433 * @brief Make a new std::shared_ptr<T> with arguments extracted from a
434 * VariantMap.
435 */
436template <typename T, typename... Types, typename... ArgNames>
437std::shared_ptr<T> make_shared_from_args(VariantMap const &vals,
438 ArgNames &&...args) {
439 return std::make_shared<T>(
440 get_value<Types>(vals, std::forward<ArgNames>(args))...);
441}
442
443template <typename T>
444void set_from_args(T &dst, VariantMap const &vals, const char *name) {
445 dst = get_value<T>(vals, name);
446}
447} // namespace ScriptInterface
448
449#endif
__shared__ int pos[MAXDEPTH *THREADS5/WARPSIZE]
float N[3]
std::unordered_map< std::string, Variant > VariantMap
Definition Variant.hpp:82
T get_value_or(VariantMap const &vals, std::string const &name, T const &default_)
Get a value from a VariantMap by name, or return a default value if it does not exist.
std::shared_ptr< T > make_shared_from_args(VariantMap const &vals, ArgNames &&...args)
Make a new std::shared_ptr<T> with arguments extracted from a VariantMap.
boost::make_recursive_variant< None, bool, int, std::size_t, double, std::string, ObjectRef, Utils::Vector3b, Utils::Vector3i, Utils::Vector2d, Utils::Vector3d, Utils::Vector4d, std::vector< int >, std::vector< double >, std::vector< boost::recursive_variant_ >, std::unordered_map< int, boost::recursive_variant_ >, std::unordered_map< std::string, boost::recursive_variant_ > >::type Variant
Possible types for parameters.
Definition Variant.hpp:80
void set_from_args(T &dst, VariantMap const &vals, const char *name)
std::string demangle()
Get a human-readable name for a type.
Definition demangle.hpp:36