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
31#include <algorithm>
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 std::ranges::transform(vv, ret.begin(), get_value_helper<T>{});
225
226 return ret;
227 }
228
229 template <typename U>
230 std::enable_if_t<allow_conversion<T, U>::value, Utils::Vector<T, N>>
231 operator()(std::vector<U, std::allocator<U>> const &v) const {
232 if (N != v.size()) {
233 throw boost::bad_get{};
234 }
235 return Utils::Vector<T, N>(v);
236 }
237
238 template <typename U> Utils::Vector<T, N> operator()(U const &) const {
239 throw boost::bad_get{};
240 }
241};
242
243/* Utils::Vector<T, N> case */
244template <typename T, std::size_t N>
245struct get_value_helper<Utils::Vector<T, N>> {
246 Utils::Vector<T, N> operator()(Variant const &v) const {
247 return boost::apply_visitor(detail::vector_conversion_visitor<T, N>(), v);
248 }
249};
250
251template <typename T>
252struct GetVectorOrEmpty : boost::static_visitor<std::vector<T>> {
253 /* Catch all case -> wrong type. */
254 template <typename U> std::vector<T> operator()(U const &) const {
255 throw boost::bad_get{};
256 }
257
258 /* Standard case, correct type */
259 std::vector<T> operator()(std::vector<T> const &v) const { return v; }
260
261 template <typename V = T,
262 std::enable_if_t<!std::is_same_v<V, Variant>, bool> = true>
263 std::vector<T> operator()(std::vector<Variant> const &vv) const {
264 std::vector<T> ret(vv.size());
265
266 std::ranges::transform(vv, ret.begin(), get_value_helper<T>{});
267
268 return ret;
269 }
270};
271
272/* std::vector cases */
273template <typename T> struct get_value_helper<std::vector<T>, void> {
274 std::vector<T> operator()(Variant const &v) const {
275 return boost::apply_visitor(GetVectorOrEmpty<T>(), v);
276 }
277};
278
279template <typename K, typename T>
280struct GetMapOrEmpty : boost::static_visitor<std::unordered_map<K, T>> {
281 /* Catch all case -> wrong type. */
282 template <typename U> std::unordered_map<K, T> operator()(U const &) const {
283 throw boost::bad_get{};
284 }
285
286 /* Standard case, correct type */
287 std::unordered_map<K, T> operator()(std::unordered_map<K, T> const &v) const {
288 return v;
289 }
290
291 template <typename V = T,
292 std::enable_if_t<!std::is_same_v<V, Variant>, bool> = true>
293 std::unordered_map<K, T>
294 operator()(std::unordered_map<K, Variant> const &v) const {
295 std::unordered_map<K, T> ret;
296 for (auto it = v.begin(); it != v.end(); ++it) {
297 ret.insert({it->first, get_value_helper<T>{}(it->second)});
298 }
299 return ret;
300 }
301};
302
303/* std::unordered_map cases */
304template <typename T>
305struct get_value_helper<std::unordered_map<int, T>, void> {
306 std::unordered_map<int, T> operator()(Variant const &v) const {
307 return boost::apply_visitor(GetMapOrEmpty<int, T>(), v);
308 }
309};
310template <typename T>
311struct get_value_helper<std::unordered_map<std::string, T>, void> {
312 std::unordered_map<std::string, T> operator()(Variant const &v) const {
313 return boost::apply_visitor(GetMapOrEmpty<std::string, T>(), v);
314 }
315};
316
317/** Custom error for a conversion that fails when the value is a nullptr. */
318class bad_get_nullptr : public boost::bad_get {};
319
320/* This allows direct retrieval of a shared_ptr to the object from
321 * an ObjectRef variant. If the type is a derived type, the type is
322 * also checked.
323 */
324template <typename T>
325struct get_value_helper<
326 std::shared_ptr<T>,
327 typename std::enable_if_t<std::is_base_of_v<ObjectHandle, T>, void>> {
328 std::shared_ptr<T> operator()(Variant const &v) const {
329 auto so_ptr = boost::get<ObjectRef>(v);
330 if (!so_ptr) {
331 throw bad_get_nullptr{};
332 }
333
334 auto t_ptr = std::dynamic_pointer_cast<T>(so_ptr);
335
336 if (t_ptr) {
337 return t_ptr;
338 }
339
340 throw boost::bad_get{};
341 }
342};
343
344/**
345 * @brief Re-throw a @c boost::bad_get exception wrapped in an @ref Exception.
346 * Write a custom error message for invalid conversions due to type mismatch
347 * and due to nullptr values, possibly with context information if the variant
348 * is a container.
349 * @tparam T Which type the variant was supposed to convert to
350 */
351template <typename T>
352inline void handle_bad_get(Variant const &v, std::string const &name) {
353 auto const container_name = demangle::simplify_symbol_variant(v);
354 auto const containee_name = demangle::simplify_symbol_containee_variant(v);
355 auto const expected_containee_name =
356 demangle::simplify_symbol_containee(static_cast<T *>(nullptr));
357 auto const from_container = !containee_name.empty();
358 auto const to_container = !expected_containee_name.empty();
359 auto what = "Provided argument of type '" + container_name + "'";
360 if (not name.empty()) {
361 what += " for parameter '" + name + "'";
362 }
363 try {
364 throw;
365 } catch (bad_get_nullptr const &) {
366 auto const item_error = (to_container) ? " contains a value that" : "";
367 throw Exception(what + item_error + " is a null pointer");
368 } catch (boost::bad_get const &) {
369 auto const non_convertible = std::string(" is not convertible to ");
370 auto item_error = std::string("");
371 if (from_container and to_container) {
372 item_error += " because it contains a value that";
373 item_error += non_convertible + "'" + expected_containee_name + "'";
374 }
375 auto const target = demangle::simplify_symbol(static_cast<T *>(nullptr));
376 throw Exception(what + non_convertible + "'" + target + "'" + item_error);
377 }
378}
379
380template <typename T> T get_value(Variant const &v, std::string const &name) {
381 try {
382 return detail::get_value_helper<T>{}(v);
383 } catch (...) {
384 detail::handle_bad_get<T>(v, name);
385 throw;
386 }
387}
388
389} // namespace detail
390
391/**
392 * @brief Extract value of specific type T from a Variant.
393 *
394 * This is a wrapper around boost::get that allows us to
395 * customize the behavior for different types. This is
396 * needed e.g. to deal with containers whose elements
397 * have mixed types that are implicitly convertible
398 * to a requested type.
399 */
400template <typename T> T get_value(Variant const &v) {
401 return detail::get_value<T>(v, "");
402}
403
404/**
405 * @brief Get a value from a VariantMap by name, or throw
406 * if it does not exist or is not convertible to
407 * the target type.
408 */
409template <typename T>
410T get_value(VariantMap const &vals, std::string const &name) {
411 if (vals.count(name) == 0ul) {
412 throw Exception("Parameter '" + name + "' is missing.");
413 }
414 return detail::get_value<T>(vals.at(name), name);
415}
416
417/**
418 * @brief Get a value from a VariantMap by name, or return a default
419 * value if it does not exist.
420 */
421template <typename T>
422T get_value_or(VariantMap const &vals, std::string const &name,
423 T const &default_) {
424 if (vals.count(name)) {
425 return get_value<T>(vals.at(name));
426 }
427 return default_;
428}
429
430/**
431 * @brief Make a new std::shared_ptr<T> with arguments extracted from a
432 * VariantMap.
433 */
434template <typename T, typename... Types, typename... ArgNames>
435std::shared_ptr<T> make_shared_from_args(VariantMap const &vals,
436 ArgNames &&...args) {
437 return std::make_shared<T>(
438 get_value<Types>(vals, std::forward<ArgNames>(args))...);
439}
440
441template <typename T>
442void set_from_args(T &dst, VariantMap const &vals, const char *name) {
443 dst = get_value<T>(vals, name);
444}
445} // namespace ScriptInterface
446
447#endif
T get_value(Variant const &v)
Extract value of specific type T from a Variant.
std::unordered_map< std::string, Variant > VariantMap
Definition Variant.hpp:69
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:67
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
std::vector< T, allocator< T > > vector
Definition vector.hpp:52