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