Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ UTF-8 decoding is performed using a state machine based on Bjoern Hoehrmann's '[
- **[@jonestristand](https://github.com/jonestristand)** - Designed and implemented the `toml::path`s feature
- **[@kcsaul](https://github.com/kcsaul)** - Fixed a bug
- **[@levicki](https://github.com/levicki)** - Helped design some new features
- **[@mikomikotaishi](https://github.com/mikomikotaishi)** - Added support for C++20 modules
- **[@mikomikotaishi](https://github.com/mikomikotaishi)** - Added support for C++20 modules and custom strings
- **[@moorereason](https://github.com/moorereason)** - Reported a whole bunch of bugs
- **[@mosra](https://github.com/mosra)** - Created the awesome [m.css] used to generate the API docs
- **[@N-Dekker](https://github.com/N-Dekker)** - Added a workaround for the legacy lambda processor of MSVC 2019/2022, added `get_line`
Expand Down
1 change: 1 addition & 0 deletions fuzzing/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ clang++ -std=c++17 -O2 -DUSE_VENDORED_LIBS=1 \
tests/conformance_burntsushi_valid.cpp \
tests/conformance_iarna_invalid.cpp \
tests/conformance_iarna_valid.cpp \
tests/custom_string.cpp \
tests/formatters.cpp \
tests/for_each.cpp \
tests/impl_toml.cpp \
Expand Down
133 changes: 128 additions & 5 deletions include/toml++/impl/forward_declarations.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -407,13 +407,89 @@ TOML_IMPL_NAMESPACE_START
inline constexpr bool is_wide_string =
is_one_of<std::decay_t<T>, const wchar_t*, wchar_t*, std::wstring_view, std::wstring>;

// user-defined 'string-like' types (see toml::is_string_like), detected via expression SFINAE as any type with:
// - a const-qualified data() or c_str() member returning (something convertible to) const char*, and
// - a const-qualified length() member returning (something convertible to) size_t.

template <typename T, typename = void>
struct has_string_like_data : std::false_type
{};
template <typename T>
struct has_string_like_data<
T,
std::enable_if_t<std::is_convertible_v<decltype(std::declval<const T&>().data()), const char*>>>
: std::true_type
{};

template <typename T, typename = void>
struct has_string_like_c_str : std::false_type
{};
template <typename T>
struct has_string_like_c_str<
T,
std::enable_if_t<std::is_convertible_v<decltype(std::declval<const T&>().c_str()), const char*>>>
: std::true_type
{};

template <typename T, typename = void>
struct has_string_like_length : std::false_type
{};
template <typename T>
struct has_string_like_length<
T,
std::enable_if_t<std::is_convertible_v<decltype(std::declval<const T&>().length()), size_t>>> : std::true_type
{};

// (std::string and std::string_view would satisfy the member checks but are first-class citizens
// with their own dedicated handling, so they're excluded from the user-defined detection)
template <typename T>
inline constexpr bool is_string_like = //
std::is_class_v<remove_cvref<T>> //
&& !is_one_of<remove_cvref<T>, std::string, std::string_view> //
&& (has_string_like_data<remove_cvref<T>>::value //
|| has_string_like_c_str<remove_cvref<T>>::value) //
&& has_string_like_length<remove_cvref<T>>::value;

// the view over a string-like's character data (preferring data() where both it and c_str() are present)
template <typename T>
TOML_NODISCARD
inline std::string_view string_like_to_view(const T& str)
{
static_assert(is_string_like<T>);

if constexpr (has_string_like_data<remove_cvref<T>>::value)
return std::string_view{ static_cast<const char*>(str.data()), static_cast<size_t>(str.length()) };
else
return std::string_view{ static_cast<const char*>(str.c_str()), static_cast<size_t>(str.length()) };
}

// ... usable as a target type when retrieving a TOML string value (storage is always std::string,
// so the type must be constructible from the stored string or a view/pointer into it)
template <typename T>
inline constexpr bool string_like_is_target = is_string_like<T>
&& (std::is_constructible_v<remove_cvref<T>, std::string_view>
|| std::is_constructible_v<remove_cvref<T>, const char*, size_t>
|| std::is_constructible_v<remove_cvref<T>, const std::string&>);

// ... and whether constructing that target can throw (it is built from the first of
// std::string_view / (const char*, size_t) / const std::string& that the type supports)
template <typename T>
inline constexpr bool value_retrieval_is_nothrow = !std::is_same_v<remove_cvref<T>, std::string>
inline constexpr bool string_like_target_is_nothrow =
std::is_constructible_v<remove_cvref<T>, std::string_view>
? std::is_nothrow_constructible_v<remove_cvref<T>, std::string_view>
: (std::is_constructible_v<remove_cvref<T>, const char*, size_t>
? std::is_nothrow_constructible_v<remove_cvref<T>, const char*, size_t>
: std::is_nothrow_constructible_v<remove_cvref<T>, const std::string&>);

template <typename T>
inline constexpr bool value_retrieval_is_nothrow =
!std::is_same_v<remove_cvref<T>, std::string>
#if TOML_HAS_CHAR8
&& !std::is_same_v<remove_cvref<T>, std::u8string>
&& !std::is_same_v<remove_cvref<T>, std::u8string>
#endif

&& !is_wide_string<T>;
// user-defined string types are constructed from the stored
// std::string, so they can throw iff that construction can
&& !(string_like_is_target<T> && !string_like_target_is_nothrow<T>) && !is_wide_string<T>;

template <typename, typename>
struct copy_ref_;
Expand Down Expand Up @@ -507,12 +583,17 @@ TOML_IMPL_NAMESPACE_START
template <typename T>
struct value_traits;

// (defined alongside the other string traits, below)
template <typename T>
struct user_string_traits;

// note: enums cannot have member functions, so the enum and string-like cases are mutually exclusive
template <typename T, bool = std::is_enum_v<T>>
struct value_traits_base_selector
{
static_assert(!is_cvref<T>);

using type = default_value_traits;
using type = std::conditional_t<is_string_like<T>, user_string_traits<T>, default_value_traits>;
};
template <typename T>
struct value_traits_base_selector<T, true>
Expand Down Expand Up @@ -742,6 +823,24 @@ TOML_IMPL_NAMESPACE_START
struct value_traits<char[N]> : string_traits<char[N]>
{};

// string value_traits specializations - user-defined string-like types (see toml::is_string_like)
//
// unlike the built-in string types these are never 'native' (storage is always std::string), and the two
// directions are tracked independently, since a type may support only one of them:
// - is_losslessly_convertible_to_native => usable as an initializer (always true - the detected
// data()/c_str() + length() members provide the character data)
// - can_represent_native => usable as a retrieval target (requires a suitable constructor)
template <typename T>
struct user_string_traits
{
using native_type = std::string;
static constexpr bool is_native = false;
static constexpr bool is_losslessly_convertible_to_native = true;
static constexpr bool can_represent_native = string_like_is_target<T>;
static constexpr bool can_partially_represent_native = can_represent_native;
static constexpr auto type = node_type::string;
};

// string value_traits specializations - char8_t-based strings
#if TOML_HAS_CHAR8
template <>
Expand Down Expand Up @@ -1028,6 +1127,30 @@ TOML_NAMESPACE_START
/// \brief Metafunction for determining if a type is, or is a reference to, a toml::node_view.
template <typename T>
inline constexpr bool is_node_view = impl::is_one_of<impl::remove_cvref<T>, node_view<node>, node_view<const node>>;

/// \brief Metafunction for determining if a type is a user-defined 'string-like' type.
///
/// \detail User-defined string types are detected automatically and may be used anywhere the library
/// expects a string - as an initializer for TOML string values, and as a target type for
/// node::value(), node::value_or() and friends: \cpp
/// tbl.insert("hostname", foo::String{ "localhost" });
/// auto hostname = tbl["hostname"].value<foo::String>();
/// \ecpp
///
/// A type is detected as string-like when it has:
/// - a const-qualified `data()` or `c_str()` member returning (something convertible to) `const char*`, and
/// - a const-qualified `length()` member returning (something convertible to) `size_t`.
///
/// Being detected is sufficient to use a type as an initializer; to also be usable as a
/// retrieval target it must additionally be constructible from one of `std::string_view`,
/// `(const char*, size_t)` or `const std::string&`. Attempting to retrieve a value as a
/// string-like type without a suitable constructor is a compile error.
///
/// \note TOML string values are always stored internally as std::string; detection governs conversions
/// at the API boundary only, not the underlying storage, so retrieving a value as a user-defined
/// string type makes a copy.
template <typename T>
inline constexpr bool is_string_like = impl::is_string_like<T>;
}
TOML_NAMESPACE_END;

Expand Down
39 changes: 37 additions & 2 deletions include/toml++/impl/value.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,13 @@ TOML_DISABLE_ARITHMETIC_WARNINGS;
TOML_SA_VALUE_MESSAGE_U8STRING_VIEW \
TOML_SA_LIST_SEP "const char*" \
TOML_SA_VALUE_MESSAGE_CONST_CHAR8 \
TOML_SA_LIST_END
TOML_SA_LIST_END \
\
TOML_SA_LIST_NXT "A user-defined 'string-like' type (see toml::is_string_like)" \
TOML_SA_LIST_BEG "with const char* data() const or const char* c_str() const" \
TOML_SA_LIST_SEP "and size_t length() const" \
TOML_SA_LIST_SEP "and constructible from std::string_view, (const char*, size_t) or const std::string&" \
TOML_SA_LIST_END

#define TOML_SA_VALUE_FUNC_MESSAGE(type_arg) \
"The " type_arg " must be one of:" \
Expand All @@ -68,6 +74,12 @@ TOML_DISABLE_ARITHMETIC_WARNINGS;
TOML_SA_VALUE_MESSAGE_U8STRING_VIEW \
TOML_SA_LIST_SEP "const char*" \
TOML_SA_VALUE_MESSAGE_CONST_CHAR8 \
TOML_SA_LIST_END \
\
TOML_SA_LIST_NXT "A user-defined 'string-like' type (see toml::is_string_like)" \
TOML_SA_LIST_BEG "with const char* data() const or const char* c_str() const" \
TOML_SA_LIST_SEP "and size_t length() const" \
TOML_SA_LIST_SEP "and constructible from std::string_view, (const char*, size_t) or const std::string&" \
TOML_SA_LIST_END

// clang-format on
Expand All @@ -83,7 +95,11 @@ TOML_IMPL_NAMESPACE_START
TOML_NODISCARD
static T make(Args&&... args) noexcept(std::is_nothrow_constructible_v<T, Args&&...>)
{
if constexpr (std::is_aggregate_v<T>)
// user-defined string-likes (see toml::is_string_like) provide their character data
// via data()/c_str() + length() rather than by conversion
if constexpr (std::is_same_v<T, std::string> && sizeof...(Args) == 1u && (is_string_like<Args> && ...))
return std::string{ string_like_to_view(args)... };
else if constexpr (std::is_aggregate_v<T>)
return T{ static_cast<Args&&>(args)... };
else
return T(static_cast<Args&&>(args)...);
Expand Down Expand Up @@ -1018,6 +1034,19 @@ TOML_NAMESPACE_START
#endif
}

// char -> user-defined string-like type (see toml::is_string_like)
else if constexpr (string_like_is_target<T>)
{
// construction preference order mirrors string_like_target_is_nothrow
// (parenthesized init, matching the std::is_constructible checks)
if constexpr (std::is_constructible_v<T, std::string_view>)
return T(std::string_view{ str });
else if constexpr (std::is_constructible_v<T, const char*, size_t>)
return T(str.data(), str.length());
else
return T(str);
}

#if TOML_HAS_CHAR8

// char -> char8_t (potentially unsafe - the feature is 'experimental'!)
Expand Down Expand Up @@ -1244,6 +1273,12 @@ TOML_NAMESPACE_START
TOML_SA_LIST_SEP "const wchar_t*"
#endif
TOML_SA_LIST_END

TOML_SA_LIST_NXT "A user-defined 'string-like' type (see toml::is_string_like)"
TOML_SA_LIST_BEG "with const char* data() const or const char* c_str() const"
TOML_SA_LIST_SEP "and size_t length() const"
TOML_SA_LIST_SEP "and constructible from std::string_view, (const char*, size_t) or const std::string&"
TOML_SA_LIST_END
);

// clang-format on
Expand Down
1 change: 1 addition & 0 deletions src/modules/tomlplusplus.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export namespace toml {
using TOML_NAMESPACE::is_node_view;
using TOML_NAMESPACE::is_number;
using TOML_NAMESPACE::is_string;
using TOML_NAMESPACE::is_string_like;
using TOML_NAMESPACE::is_table;
using TOML_NAMESPACE::is_time;
using TOML_NAMESPACE::is_value;
Expand Down
Loading
Loading