Skip to content

Commit c4d2565

Browse files
authored
#959: Replace type_name template with name_type(). (#970)
Fixes: #959 This should make it easier to override a type name with a manually written, human-friendly one such as std::string instead of the whole std::basic_string<char, ...> incantation; inline those to reduce duplication; and consistently make it a std::string_view. With that latter point, it abstracts away the underlying storage. Which does mean that sometimes with template types you may need to construct the names in a separate place just so name_type() can return a view. But that's probably better than storing each individual name in its own std::string, and allows for more optimisations.
1 parent 9ba4ba3 commit c4d2565

18 files changed

+249
-105
lines changed

include/pqxx/array.hxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ private:
427427
std::format(
428428
"Array contains a null {}. Consider making it an array of "
429429
"std::optional<{}> instead.",
430-
type_name<ELEMENT>, type_name<ELEMENT>),
430+
name_type<ELEMENT>(), name_type<ELEMENT>()),
431431
loc};
432432
}
433433
else

include/pqxx/doc/datatypes.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,11 @@ First off, of course, you need a C++ type. It may be your own, but it doesn't
7474
have to be. It could be a type from a third-party library, or even one from
7575
the standard library that libpqxx does not yet support.
7676

77-
First thing to do is specialise the `pqxx::type_name` variable to give the type
78-
a human-readable name. That allows libpqxx error messages and such to talk
79-
about the type. If you don't define a name, libpqxx will try to figure one out
80-
with some help from the compiler, but it may not always be easy to read.
77+
First thing to do is specialise the `pqxx::name_type()` function to give the
78+
type a human-readable name. That allows libpqxx error messages and such to
79+
talk about the type. If you don't define a name, libpqxx will try to figure
80+
one out with some help from the compiler, but it may not always be easy to
81+
read.
8182

8283
Then, does your type have a built-in null value? For example, a `char *` can
8384
be null on the C++ side. Or some types are _always_ null, such as `nullptr`.
@@ -112,8 +113,8 @@ The library also provides specialisations for `std::optional<T>`,
112113
have conversions for `T`, you'll also automatically have conversions for those.
113114

114115

115-
Specialise `type_name`
116-
----------------------
116+
Specialise `name_type()`
117+
------------------------
117118

118119
(This is a feature that should disappear once we have introspection in the C++
119120
language.)
@@ -122,15 +123,16 @@ When errors happen during conversion, libpqxx will compose error messages for
122123
the user. Sometimes these will include the name of the type that's being
123124
converted.
124125

125-
To tell libpqxx the name of each type, there's a template variable called
126-
`pqxx::type_name`. For any given type `T`, it should have a specialisation
126+
To tell libpqxx the name of each type, there's a function template called
127+
`pqxx::name_type()`. For any given type `T`, it should have a specialisation
127128
that provides that `T`'s human-readable name:
128129

129130
```cxx
130131
// T is your type.
131132
namespace pqxx
132133
{
133-
template<> std::string const type_name<T>{"My T type's name"};
134+
template<> inline std::string_view
135+
name_type<T>(){ return "My T type's name"; };
134136
}
135137
```
136138

include/pqxx/field.hxx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ public:
227227
if (is_null())
228228
{
229229
if constexpr (not nullness<T>::has_null)
230-
internal::throw_null_conversion(type_name<T>, c.loc);
230+
internal::throw_null_conversion(name_type<T>(), c.loc);
231231
else
232232
return nullness<T>::null();
233233
}
@@ -255,7 +255,7 @@ public:
255255

256256
// There's no such thing as a null SQL array.
257257
if (is_null())
258-
internal::throw_null_conversion(type_name<array_type>, loc);
258+
internal::throw_null_conversion(name_type<array_type>(), loc);
259259
else
260260
return array_type{this->view(), this->m_home.m_encoding, loc};
261261
}
@@ -366,7 +366,7 @@ inline bool field::to<zview>(zview &obj, zview const &default_value, ctx) const
366366
template<> inline zview field::as<zview>(ctx c) const
367367
{
368368
if (is_null())
369-
internal::throw_null_conversion(type_name<zview>, c.loc);
369+
internal::throw_null_conversion(name_type<zview>(), c.loc);
370370
return zview{c_str(), size()};
371371
}
372372

@@ -505,7 +505,7 @@ template<typename T> inline T from_string(field const &value, ctx c = {})
505505
if constexpr (nullness<T>::has_null)
506506
return nullness<T>::null();
507507
else
508-
internal::throw_null_conversion(type_name<T>, c.loc);
508+
internal::throw_null_conversion(name_type<T>(), c.loc);
509509
}
510510
else
511511
{

include/pqxx/internal/array-composite.hxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ inline void parse_composite_field(
202202
throw conversion_error{
203203
std::format(
204204
"Can't read composite field {}: C++ type {} does not support nulls.",
205-
to_string(index), type_name<T>),
205+
to_string(index), name_type<T>()),
206206
loc};
207207
break;
208208

include/pqxx/internal/conversions.hxx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ generic_into_buf(char *begin, char *end, T const &value, ctx c = {})
101101
auto const len = std::size(text) + 1;
102102
if (std::cmp_greater(len, space))
103103
throw conversion_overrun{
104-
std::format("Not enough buffer space to insert {}. ", type_name<T>) +
104+
std::format("Not enough buffer space to insert {}. ", name_type<T>()) +
105105
state_buffer_overrun(space, len),
106106
c.loc};
107107
std::memmove(begin, std::data(text), len);
@@ -121,7 +121,7 @@ generic_into_buf(std::span<char> buf, T const &value, ctx c = {})
121121
auto const len = std::size(text) + 1;
122122
if (std::cmp_greater(len, space))
123123
throw conversion_overrun{
124-
std::format("Not enough buffer space to insert {}. ", type_name<T>) +
124+
std::format("Not enough buffer space to insert {}. ", name_type<T>()) +
125125
state_buffer_overrun(space, len),
126126
c.loc};
127127
std::memmove(begin, std::data(text), len);
@@ -841,7 +841,7 @@ struct string_traits<std::unique_ptr<T, Args...>>
841841
ctx c = {})
842842
{
843843
if (not value)
844-
internal::throw_null_conversion(type_name<std::unique_ptr<T>>, c.loc);
844+
internal::throw_null_conversion(name_type<std::unique_ptr<T>>(), c.loc);
845845
return begin + pqxx::into_buf({begin, end}, *value);
846846
}
847847

@@ -850,7 +850,7 @@ struct string_traits<std::unique_ptr<T, Args...>>
850850
ctx c = {})
851851
{
852852
if (not value)
853-
internal::throw_null_conversion(type_name<std::unique_ptr<T>>, c.loc);
853+
internal::throw_null_conversion(name_type<std::unique_ptr<T>>(), c.loc);
854854
return pqxx::to_buf({begin, end}, *value);
855855
}
856856

@@ -905,14 +905,14 @@ template<typename T> struct string_traits<std::shared_ptr<T>>
905905
to_buf(char *begin, char *end, std::shared_ptr<T> const &value, ctx c = {})
906906
{
907907
if (not value)
908-
internal::throw_null_conversion(type_name<std::shared_ptr<T>>, c.loc);
908+
internal::throw_null_conversion(name_type<std::shared_ptr<T>>(), c.loc);
909909
return pqxx::to_buf({begin, end}, *value, c);
910910
}
911911
static char *
912912
into_buf(char *begin, char *end, std::shared_ptr<T> const &value, ctx c = {})
913913
{
914914
if (not value)
915-
internal::throw_null_conversion(type_name<std::shared_ptr<T>>, c.loc);
915+
internal::throw_null_conversion(name_type<std::shared_ptr<T>>(), c.loc);
916916
return begin + pqxx::into_buf({begin, end}, *value, c);
917917
}
918918
static std::size_t size_buffer(std::shared_ptr<T> const &value) noexcept
@@ -1140,7 +1140,7 @@ template<typename TYPE> inline std::string to_string(TYPE const &value, ctx c)
11401140
{
11411141
if (is_null(value))
11421142
throw conversion_error{
1143-
std::format("Attempt to convert null to a string.", type_name<TYPE>),
1143+
std::format("Attempt to convert null to a string.", name_type<TYPE>()),
11441144
c.loc};
11451145

11461146
if constexpr (nullness<std::remove_cvref_t<TYPE>>::always_null)
@@ -1187,7 +1187,7 @@ inline void into_string(T const &value, std::string &out, ctx c = {})
11871187
{
11881188
if (is_null(value))
11891189
throw conversion_error{
1190-
std::format("Attempt to convert null {} to a string.", type_name<T>),
1190+
std::format("Attempt to convert null {} to a string.", name_type<T>()),
11911191
c.loc};
11921192

11931193
// We can't just reserve() data; modifying the terminating zero leads to

include/pqxx/internal/stream_query.hxx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,14 +282,14 @@ private:
282282
if (std::data(text) != nullptr)
283283
throw conversion_error{std::format(
284284
"Streaming a non-null value into a {}, which must always be null.",
285-
type_name<field_type>)};
285+
name_type<field_type>())};
286286
}
287287
else if (std::data(text) == nullptr)
288288
{
289289
if constexpr (nullity::has_null)
290290
return nullity::null();
291291
else
292-
internal::throw_null_conversion(type_name<field_type>, loc);
292+
internal::throw_null_conversion(name_type<field_type>(), loc);
293293
}
294294
else
295295
{

include/pqxx/strconv.hxx

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,6 @@
3030
#include "pqxx/zview.hxx"
3131

3232

33-
namespace pqxx::internal
34-
{
35-
/// Attempt to demangle @c std::type_info::name() to something human-readable.
36-
PQXX_LIBEXPORT std::string demangle_type_name(char const[]);
37-
} // namespace pqxx::internal
38-
39-
4033
namespace pqxx
4134
{
4235
/**
@@ -64,18 +57,6 @@ namespace pqxx
6457
*/
6558
//@{
6659

67-
/// A human-readable name for a type, used in error messages and such.
68-
/** Actually this may not always be very user-friendly. It uses
69-
* @c std::type_info::name(). On gcc-like compilers we try to demangle its
70-
* output. Visual Studio produces human-friendly names out of the box.
71-
*
72-
* This variable is not inline. Inlining it gives rise to "memory leak"
73-
* warnings from asan, the address sanitizer, possibly from use of
74-
* @c std::type_info::name.
75-
*/
76-
template<typename TYPE>
77-
std::string const type_name{internal::demangle_type_name(typeid(TYPE).name())};
78-
7960

8061
/// Traits describing a type's "null value," if any.
8162
/** Some C++ types have a special value or state which correspond directly to
@@ -613,13 +594,9 @@ private:
613594
} // namespace pqxx::internal
614595

615596

616-
// We used to inline type_name<ENUM>, but this triggered a "double free" error
617-
// on program exit, when libpqxx was built as a shared library on Debian with
618-
// gcc 12.
619-
620597
/// Macro: Define a string conversion for an enum type.
621-
/** This specialises the @c pqxx::string_traits template, so use it in the
622-
* @c ::pqxx namespace.
598+
/** This specialises the @ref pqxx::string_traits and @ref pqxx::name_type
599+
* templates, so use it in the @ref pqxx namespace.
623600
*
624601
* For example:
625602
*
@@ -630,12 +607,15 @@ private:
630607
* int main() { std::cout << pqxx::to_string(xa) << std::endl; }
631608
*/
632609
#define PQXX_DECLARE_ENUM_CONVERSION(ENUM) \
633-
template<> struct string_traits<ENUM> : pqxx::internal::enum_traits<ENUM> \
634-
{}; \
635-
template<> inline std::string_view const type_name<ENUM> \
610+
template<> \
611+
[[maybe_unused, deprecated("Use name_type() instead of type_name.")]] \
612+
inline std::string_view const type_name<ENUM>{#ENUM}; \
613+
template<> constexpr inline std::string_view name_type<ENUM>() \
636614
{ \
637-
#ENUM \
638-
}
615+
return #ENUM; \
616+
} \
617+
template<> struct string_traits<ENUM> : pqxx::internal::enum_traits<ENUM> \
618+
{}
639619

640620

641621
namespace pqxx

include/pqxx/stream_from.hxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ inline void stream_from::extract_value(Tuple &t, sl loc) const
358358
if constexpr (nullity::has_null)
359359
std::get<index>(t) = nullity::null();
360360
else
361-
internal::throw_null_conversion(type_name<field_type>, loc);
361+
internal::throw_null_conversion(name_type<field_type>(), loc);
362362
}
363363
else
364364
{

include/pqxx/types.hxx

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,123 @@ struct from_query_t
158158
} // namespace pqxx
159159

160160

161+
namespace pqxx::internal
162+
{
163+
/// Attempt to demangle @c std::type_info::name() to something human-readable.
164+
PQXX_LIBEXPORT std::string demangle_type_name(char const[]);
165+
} // namespace pqxx::internal
166+
167+
168+
namespace pqxx
169+
{
170+
/// A human-readable name for a type, used in error messages and such.
171+
/** Actually this may not always be very user-friendly. It uses
172+
* @c std::type_info::name(). On gcc-like compilers we try to demangle its
173+
* output. Visual Studio produces human-friendly names out of the box.
174+
*
175+
* This variable is not inline. Inlining it gives rise to "memory leak"
176+
* warnings from asan, the address sanitizer, possibly from use of
177+
* @c std::type_info::name.
178+
*/
179+
#include "pqxx/internal/ignore-deprecated-pre.hxx"
180+
template<typename TYPE>
181+
[[deprecated("Use name_type() instead.")]]
182+
std::string const type_name{
183+
pqxx::internal::demangle_type_name(typeid(TYPE).name())};
184+
#include "pqxx/internal/ignore-deprecated-post.hxx"
185+
186+
187+
/// Return human-readable name for `TYPE`.
188+
template<typename TYPE> inline std::string_view name_type()
189+
{
190+
#include "pqxx/internal/ignore-deprecated-pre.hxx"
191+
return type_name<TYPE>;
192+
#include "pqxx/internal/ignore-deprecated-post.hxx"
193+
}
194+
195+
196+
/// Specialisation to save on startup work & produce friendlier output.
197+
template<> constexpr inline std::string_view name_type<std::string>()
198+
{
199+
return "std::string";
200+
}
201+
/// Specialisation to save on startup work & produce friendlier output.
202+
template<> constexpr inline std::string_view name_type<std::string_view>()
203+
{
204+
return "std::string_view";
205+
}
206+
template<> constexpr inline std::string_view name_type<char const *>()
207+
{
208+
return "char const *";
209+
}
210+
/// Specialisation to save on startup work.
211+
template<> constexpr inline std::string_view name_type<bool>()
212+
{
213+
return "bool";
214+
}
215+
/// Specialisation to save on startup work.
216+
template<> constexpr inline std::string_view name_type<short>()
217+
{
218+
return "short";
219+
}
220+
/// Specialisation to save on startup work.
221+
template<> constexpr inline std::string_view name_type<int>()
222+
{
223+
return "int";
224+
}
225+
/// Specialisation to save on startup work.
226+
template<> constexpr inline std::string_view name_type<long>()
227+
{
228+
return "long";
229+
}
230+
/// Specialisation to save on startup work.
231+
template<> constexpr inline std::string_view name_type<long long>()
232+
{
233+
return "long long";
234+
}
235+
/// Specialisation to save on startup work.
236+
template<> constexpr inline std::string_view name_type<unsigned short>()
237+
{
238+
return "short";
239+
}
240+
/// Specialisation to save on startup work.
241+
template<> constexpr inline std::string_view name_type<unsigned>()
242+
{
243+
return "unsigned";
244+
}
245+
/// Specialisation to save on startup work.
246+
template<> constexpr inline std::string_view name_type<unsigned long>()
247+
{
248+
return "unsigned long";
249+
}
250+
/// Specialisation to save on startup work.
251+
template<> constexpr inline std::string_view name_type<unsigned long long>()
252+
{
253+
return "unsigned long long";
254+
}
255+
/// Specialisation to save on startup work.
256+
template<> constexpr inline std::string_view name_type<float>()
257+
{
258+
return "float";
259+
}
260+
/// Specialisation to save on startup work.
261+
template<> constexpr inline std::string_view name_type<double>()
262+
{
263+
return "double";
264+
}
265+
/// Specialisation to save on startup work.
266+
template<> constexpr inline std::string_view name_type<long double>()
267+
{
268+
return "long double";
269+
}
270+
/// Specialisation to save on startup work.
271+
template<> constexpr inline std::string_view name_type<std::nullptr_t>()
272+
{
273+
return "std::nullptr_t";
274+
}
275+
} // namespace pqxx
276+
277+
161278
namespace pqxx::internal
162279
{
163280
/// Concept: one of the "char" types.

0 commit comments

Comments
 (0)