Skip to content

feat: Allow decoding for types without default constructor - #1479

Open
SGSSGene wants to merge 1 commit into
jbeder:masterfrom
SGSSGene:feat/not-default-decoder
Open

feat: Allow decoding for types without default constructor#1479
SGSSGene wants to merge 1 commit into
jbeder:masterfrom
SGSSGene:feat/not-default-decoder

Conversation

@SGSSGene

@SGSSGene SGSSGene commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

This PR is inspired by #1010, but instead of creating a new customization point via decode_dispatcher it reuses convert by using convert<std::optional<T>> specialization for non-default constructible classes.

If cmake variable YAML_CPP_USE_OPTIONAL is set (automatically set if target is c++17 or above) this new feature is activated. It can be deactivated by setting YAML_CPP_USE_OPTIONAL to false to force old behavior if desired.

When decoding a type T, it will use convert<std::optional<T>> and forward to convert<T> if not available.
The signature requirements are as before but by using the additional std::optional it allows us to delay the construction of our type T.
Assume we have some type without default constructor:

class Vec3 {
  double x, y, z;
public:
  Vec3(double x, double y, double z} : x{x}, y{y}, z{z} {}
};

you could write

namespace YAML {
template<>
struct convert<std::optional<Vec3>> {
  static bool decode(const Node& node, std::optional<Vec3>& rhs) {
    if(!node.IsSequence() || node.size() != 3) {
      return false;
    }
    rhs.emplace(
        node[0].as<double>(),
        node[1].as<double>(),
        node[2].as<double>()
    );
    return true;
  }
};
}

To implement encoding for Vec3 one must still implement convert<Vec3> with the encode method.

Notes:
fixes #973 #993
alternative for PR #1010 and #1087

PR #1087 breaks API and relies on exception paths which I don't like, since this seems like normal control flow to me.

@SGSSGene
SGSSGene force-pushed the feat/not-default-decoder branch from 2897332 to 41be05a Compare August 10, 2026 09:27
@SGSSGene
SGSSGene requested a review from jbeder August 10, 2026 09:29
@SGSSGene
SGSSGene force-pushed the feat/not-default-decoder branch 4 times, most recently from 9522ee9 to d1f8ebc Compare August 10, 2026 10:46
@SGSSGene

SGSSGene commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator Author

Maybe it also enough to consider it as a fix for #506

@alex-thiessen-for-siemens alex-thiessen-for-siemens left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't like that interface, could we have something like template<> struct convert<std::expected<Vec3>> static auto decode(const Node& node) -> std::expected<Vec3> instead?

Comment thread docs/Tutorial.md
```

## Non-default constructible types (requires c++17 and newer)
Yaml-cpp also supports types that are not default constructible. For this one need to specialize `YAML::convert<std::optional<>>`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default constructible -> default-constructible

Comment thread docs/Tutorial.md
```

## Non-default constructible types (requires c++17 and newer)
Yaml-cpp also supports types that are not default constructible. For this one need to specialize `YAML::convert<std::optional<>>`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this one need to specialize -> For this, one needs to specialize

Comment thread docs/Tutorial.md
};
```

you could write (for encoding the previous `convert<Vec3>` with the `encode` method is required)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for encoding the previous -> for encoding, the previous

Comment thread docs/Tutorial.md
namespace YAML {
template<>
struct convert<std::optional<Vec3>> {
static bool decode(const Node& node, std::optional<Vec3>& rhs) {

@alex-thiessen-for-siemens alex-thiessen-for-siemens Aug 24, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this interface now has two bools (one return value, one std::optional-embedded), what's their combinatory semantics?

Comment thread docs/Tutorial.md
// Implementation of convert::{encode,decode} for all classes derived from or being A
namespace YAML {
template<typename T>
template<typename T>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to clean all whitespace in a separate effort, adding a static check for that?

// if available and supported include required header
// otherwise remove YAML_CPP_USE_OPTIONAL definition
#ifdef YAML_CPP_USE_OPTIONAL
#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a weird check, maybe C++20 should be the base line so that more straightforward feature-test macros could be used instead.

Comment thread docs/Tutorial.md
node["end"] = Vec3(2, -1, 0);
```

## Non-default constructible types (requires c++17 and newer)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c++17 and newer -> C++17 or newer

Comment thread docs/Tutorial.md
```cpp
namespace YAML {
template<>
struct convert<std::optional<Vec3>> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd doubt that std::optional is a great choice here, std::expected seems to be more appropriate, as it enables users to provide informative failure data more easily than e.g. via exception handling. It's been available in mainstream toolchains for 3-4 years now.

@SGSSGene

Copy link
Copy Markdown
Collaborator Author

@alex-thiessen-for-siemens
Thank you for your great feedback!

I think there is still plenty of room for discussion about this (or the other PRs).
I agree that std::expected has a lot more charm than std::optional. Also I agree that auto decode(Node& node) -> resultType is much nice than bool decode(Node& node, resultType& type).

Currently yaml-cpp is based on c++11. I feel weird to add features that only work with newer c++ versions. Which is also why I am not sure if it really matters if we use std::optional (c++17) or std::expected (c++23). We also could introduce a custom YAML-type that kind of works like std::optional but is c++11 compatible....

With a similar argument I used bool decode(Node& node, resultType& type) this is much easier to implement with c++17 (or older). The signature auto decode(Node& node) -> resultType is only manageable with c++20 and concept is being used. Which might be another argument going all in for c++23 and use std::expected.

Opinions from anyone are very welcome. Especially from people who would have concrete use cases and can easier imagine the pro/cons of the different APIs.

@SGSSGene

Copy link
Copy Markdown
Collaborator Author

I really don't like that interface, could we have something like template<> struct convert<std::expected<Vec3>> static auto decode(const Node& node) -> std::expected<Vec3> instead?

If we switch to c++23 we could even go for specializing Vec3 instead of std::expected<Vec3> which I think is even prettier:

template<> struct convert<Vec3> {
    static auto decode(const Node& node) -> std::expected<Vec3>
}

@alex-thiessen-for-siemens

Copy link
Copy Markdown

Sounds like a policy decision could be handy regarding how to proceed with adding functionality which would benefit or require recent C++ versions' features, like std::optional, std::expected or reflection.

What this PR brings is added convenience and performance of not having to dynamically allocate non-default-constructible objects, right? That would count as optimization which can be offered exclusively to users of recent C++ versions, IMHO. This would avoid the awkward double-bool situation here.

Those who stick to old yet battle-tested toolchain versions don't usually crave for most convenient library features, I'd suggest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Better support for "default"

2 participants