fkYAML icon indicating copy to clipboard operation
fkYAML copied to clipboard

Defect in c-tor

Open KristianIvarsson opened this issue 5 months ago • 5 comments

Description

There seem to be some flaw in move c-tor that makes the type not become as explected

Maybe a simple std::variant and using default implementation would be more simple

Reproduction steps

   const fkyaml::node a = fkyaml::node::mapping();
   const fkyaml::node b{ fkyaml::node::mapping()};
   assert( a.get_type() == b.get_type());

Expected vs. actual results

Expected

assert( b.get_type() == fkyaml::node::MAPPING);

Actual

assert( b.get_type() == fkyaml::node::SEQUENCE);

Minimal code example


Error messages


Compiler and operating system

g++13 Ubuntu

Library version

g++13

Validation

KristianIvarsson avatar Jul 30 '25 08:07 KristianIvarsson

Hi! Thank you for reporting! This (probably) isn't a bug in the library, but a result of how different compilers handle move semantics:

const fkyaml::node b{ fkyaml::node::mapping()};

GCC and Clang perform a move from the temporary returned by mapping(), which clears internal state (like m_attrs) in the move constructor. MSVC, on the other hand, aggressively applies copy elision, so no move happens - the temporary is constructed directly into b, preserving the type.

That's why get_type() returns different results on each compiler. Best fix is avoid brace initialization with temporaries. Use assignment instead:

const auto b = fkyaml::node::mapping(); // Consistent across all compilers

Let me know if you'd prefer a patch that preserves type info after move!

sndth avatar Aug 03 '25 16:08 sndth

Yes, I guess RVO is applied in the former construct, but the observable behaviour should be the same in the latter case

I'm not sure if users always have the possibility to avoid brace initialization (e.g. a c-tor for a const member or such)

Let me know if you'd prefer a patch that preserves type info after move!

I'm not 100% sure what you mean by preserve, but isn't that what should happen? Shouldn't every property of rhs be moved/copied into lhs?

KristianIvarsson avatar Aug 04 '25 07:08 KristianIvarsson

I looked a bit more into this @sndth

When using brace-initialization, the basic_node(initializer_list_t init) c-tor is (naturally) invoked as well. Removing that c-tor make things work as expected. Maybe there's a flaw in that one ?

The node_ref->is_sequence() looks a bit fishy in

        bool is_mapping =
            std::all_of(init.begin(), init.end(), [](const detail::node_ref_storage<basic_node>& node_ref) {
                // Do not use is_sequence_impl() since node_ref may be an anchor or alias.
                return node_ref->is_sequence() && node_ref->size() == 2;
            });

Is the initializer-list c-tor really needed ?

Maybe it's too much work (refactoring), but I'd go with some standard data structure for all this and then not have any explicit c-tors or assignment operators what so ever

KristianIvarsson avatar Aug 05 '25 12:08 KristianIvarsson

Hi! Sorry for the long wait! 😅 I've prepared a fix for GCC so that braced-init behaves like MSVC for empty collections.

With this patch, both:

const node a = node::mapping();
const node b{ node::mapping() };

and

const node a = node::sequence();
const node b{ node::sequence() };

now produce the same result on GCC and MSVC.

The special-case unwrap is applied only when the initializer list contains exactly one element and that element is an empty mapping or empty sequence with no extra properties (tags, anchors, etc.). This resolves the original { mapping() }/{ sequence() } mismatch without breaking any of the existing initializer list semantics.

It doesn't change the behavior for scalars - for example:

const node a = node::boolean_type(true);  // Scalar
const node b{ node::boolean_type(true) }; // Still a 1-element sequence

MSVC sometimes elides here and makes it "look" like a scalar move, but that's not portable and is out of scope for this fix. If needed, we can discuss an opt-in to unwrap scalars as well, but by default braces are meant for collections and parentheses for single values.

Could you try this branch on your side and see if it solves your original case?

sndth avatar Aug 13 '25 20:08 sndth

Okey, cool

I will try it out someday (hopefully soon)

Using braces for initialization is called Uniform Initialization and is not only meant for collections (according to the standard)

KristianIvarsson avatar Aug 14 '25 18:08 KristianIvarsson