Library of Assembled Shared Sources
export_traits_variant.h
Go to the documentation of this file.
1/** @file
2 * @author Bram de Greve (bram@cocamware.com)
3 * @author Tom De Muer (tom@cocamware.com)
4 *
5 * *** BEGIN LICENSE INFORMATION ***
6 *
7 * The contents of this file are subject to the Common Public Attribution License
8 * Version 1.0 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://lass.sourceforge.net/cpal-license. The License is based on the
11 * Mozilla Public License Version 1.1 but Sections 14 and 15 have been added to cover
12 * use of software over a computer network and provide for limited attribution for
13 * the Original Developer. In addition, Exhibit A has been modified to be consistent
14 * with Exhibit B.
15 *
16 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
17 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
18 * language governing rights and limitations under the License.
19 *
20 * The Original Code is LASS - Library of Assembled Shared Sources.
21 *
22 * The Initial Developer of the Original Code is Bram de Greve and Tom De Muer.
23 * The Original Developer is the Initial Developer.
24 *
25 * All portions of the code written by the Initial Developer are:
26 * Copyright (C) 2022-2025 the Initial Developer.
27 * All Rights Reserved.
28 *
29 * Contributor(s):
30 *
31 * Alternatively, the contents of this file may be used under the terms of the
32 * GNU General Public License Version 2 or later (the GPL), in which case the
33 * provisions of GPL are applicable instead of those above. If you wish to allow use
34 * of your version of this file only under the terms of the GPL and not to allow
35 * others to use your version of this file under the CPAL, indicate your decision by
36 * deleting the provisions above and replace them with the notice and other
37 * provisions required by the GPL License. If you do not delete the provisions above,
38 * a recipient may use your version of this file under either the CPAL or the GPL.
39 *
40 * *** END LICENSE INFORMATION ***
41 */
42
43#pragma once
44
45#include "python_common.h"
46#include "export_traits.h"
47
48#if LASS_HAVE_STD_VARIANT
49
50#include <variant>
51
52namespace lass
53{
54namespace python
55{
56
57/** std::monostate always maps to None
58 *
59 * @ingroup PyExportTraits
60 */
61template <>
62struct PyExportTraits<std::monostate>
63{
64 static PyObject* build(std::monostate)
65 {
66 Py_RETURN_NONE;
67 }
68 static int get(PyObject* obj, std::monostate&)
69 {
70 if (obj != Py_None)
71 {
72 PyErr_SetString(PyExc_TypeError, "Not None");
73 return 1;
74 }
75 return 0;
76 }
77};
78
79
80namespace impl
81{
82
83template <typename Variant, typename ...T> struct PyExportTraitsVariantImpl;
84
85template <typename Variant, typename Head, typename ...Tail>
86struct PyExportTraitsVariantImpl<Variant, Head, Tail...>
87{
88 static int get(PyObject* obj, Variant& v)
89 {
90 Head head;
91 if (PyExportTraits<Head>::get(obj, head) == 0)
92 {
93 v = std::move(head);
94 return 0;
95 }
96 PyErr_Clear();
97 return PyExportTraitsVariantImpl<Variant, Tail...>::get(obj, v);
98 }
99};
100
101template <typename Variant, typename ...Tail>
102struct PyExportTraitsVariantImpl<Variant, bool, Tail...>
103{
104 // bools get special treatment.
105 //
106 // Normally, bools accept anything that evaluates to bool, and anything that is
107 // true-ish will evaluate to true. This requires to put them at the end of the
108 // alternative list, but then you would never be able to differentiate between
109 // bool and ints, as bools are ints too ...
110 //
111 // If bool is part of a variant, we only accept them strictly as bools, so you can
112 // put them first in your list.
113 static int get(PyObject* obj, Variant& v)
114 {
115 if (PyBool_Check(obj))
116 {
117 v = obj == Py_True
118 ? true
119 : false;
120 return 0;
121 }
122 return PyExportTraitsVariantImpl<Variant, Tail...>::get(obj, v);
123 }
124};
125
126template <typename Variant>
127struct PyExportTraitsVariantImpl<Variant>
128{
129 static int get(PyObject* /*obj*/, Variant& /*v*/)
130 {
131 PyErr_SetString(PyExc_TypeError, "Unable to cast to variant");
132 return 1;
133 }
134};
135
136}
137
138
139/** Exports std::variant using the PyExportTraits of individual types.
140 *
141 * std::monostate is always mapped to None
142 *
143 * When getting an std::variant from Python, the order of the alternative types
144 * is important. Each alternative will be tried from left to right, and the
145 * first match will determine the std::variant alternative type.
146 *
147 * Here are some possible pitfalls to consider:
148 *
149 * - None will always match to std::monostate. If None is also a valid value
150 * for any of the following alternatives, those types will never be considered
151 * (as std::monostate must be the first alternative).
152 *
153 * - Integers are valid floats, and floats are valid complex numbers. If complex
154 * or a floating point is followed by an integer, then an integer will always
155 * match the complex or floating point. For example, the following will not
156 * have the desired outcome:
157 *
158 * std::variant<std::complex<float>, double, signed int>
159 *
160 * Better is to reverse the order:
161 *
162 * std::variant<signed int, double, std::complex<float>>
163 *
164 * - A Python str is also a sequence of str. If a std::variant has a container
165 * of std::string preceding std::string itself as alternative, then a Python
166 * string will always match the container. For example, the following will not
167 * have the desired outcome:
168 *
169 * std::variant<std::vector<std::string>, std::string>
170 *
171 * Better is to put the std::string first:
172 *
173 * std::variant<std::string, std::vector<std::string>>
174 *
175 * NOTE: std::pair and std::tuple are also sequences in Python. So
176 * std::pair<std::string, std::string> will match any two character string.
177 *
178 * @ingroup PyExportTraits
179 */
180template <typename... T>
181struct PyExportTraits< std::variant<T...> >
182{
183 constexpr static const char* py_typing = "Union[T...]";
184
185 using TVariant = std::variant<T...>;
186
187 static PyObject* build(const TVariant& v)
188 {
189 return std::visit([](auto&& arg) -> PyObject* {
190 using U = std::decay_t<decltype(arg)>;
191 return PyExportTraits<U>::build(arg);
192 }, v);
193 }
194
195 static int get(PyObject* obj, TVariant& v)
196 {
197 return impl::PyExportTraitsVariantImpl<TVariant, T...>::get(obj, v);
198 }
199};
200
201}
202}
203
204#endif
Comprehensive C++ to Python binding library.
Library for Assembled Shared Sources.
Definition config.h:53
by copy, general case assumes shadow type or PyObjectPlus based type.