Rosidl message builder utilize default field values```

Hello ROS community!

I would like to discuss a usage and a potential improvement of a message builders for C++.
Not sure that this Idea will be useful in any way, so I’ve decided to start a topic here to discuss it.

Problem at hand

Say I have a message my_msgs/msg/Example.msg, that goes something like this:

# Some non-default value
string str

# String with default value
string def_str "This is a default string value"

In my C++ code I might construct this message in two ways:

  1. Using a constructor and filling the data afterwards
  2. Using a builder

Constructor approach

Using constructor with no values might look something like this

...
auto ex_msg = Example();
ex_msg.str = "My non-default string value";
...

Builder approach

It seems to me that it is a dedicated design approach, but it feels kind of cumbersome.

...
auto ex_msg = my_msgs::build<Example> ()
    .str("My non-default string value")
    .def_str("Need to set value for field with default value");
...

Python

In Pythhon I may utilize a much simpler approach

from my_msgs.msgs import Example

...
    ex_msg = Example(
        str="My non-default string value"
    )

Solution approaches, as I see the

There are a few approaches coming to my mind, for solving this problem.

  1. Introduce changes to generated C++ code for builder. But I’m not quite sure what this may look like yet.
  2. Add new interface. Like an another generated better_builder(lol) class for messages.
  3. Give C++ users an option to use message constructors as Python users do.

Potential pitfalls with these options

  1. Existing interface is not designed for such changes. Might take some dirty hacks to do something like that
  2. New interface means more code to support, new bugs etc. I’m not sure that this is really necessary for a such unpopular problem

Other discussions that I’ve found

I’ve found this discussion on rosidl’s github page. It is mentioned there that a syntax like this

auto my_header = std_msgs::msg::Header({.frame_id = "hello"});

may be added with the introduction of C++20, but it seems that C++20 may stay unsupported for quite some time in the future(there was a discussion at ros2 github, but I have failed to find it again. Will add to the comments if I’ll find it later), and I’m not sure if that will solve the default-value message fields issue in general.

I’m confused. The proposed feature exists for string

string PLC_STATE=/hal/plc_state

Hello JM_ROS!

The problem, I aim to address is C++ message builder requiring, at compile time, a user to provide a value for a message field that already has a default value.

What I mean is that in this code snippet

auto ex_msg = my_msgs::build<Example> ()
    .str("My non-default string value")
    .def_str("Need to set value for field with default value");

at a third string a constructor for a field def_str is called, and there is no way to skip it, even if I set a default value for that field in the message description. Like this

# String with default value
string def_str "This is a default string value"

I am currently using a Humble distro. Do you mean that this problem is solved in Iron, or in Rolling? That would actually be great :slight_smile:

Ahh okay, I thought you wanted string constants.

I just tested this.

Led.msg
string name "test"

generates a constructor

  explicit Led_(rosidl_runtime_cpp::MessageInitialization _init = rosidl_runtime_cpp::MessageInitialization::ALL)
  {
    if (rosidl_runtime_cpp::MessageInitialization::ALL == _init ||
      rosidl_runtime_cpp::MessageInitialization::DEFAULTS_ONLY == _init)
    {
      this->name = "test";
    } else if (rosidl_runtime_cpp::MessageInitialization::ZERO == _init) {
      this->name = "";
    }
  }

So name is default initialized with ‘test’ and using this snippet

auto foo = msg::Led().set__foo("Bar");

will yield the result you want. But you won’t get any check, if you initialized all members.

Using the builder API you need to pass all arguments. I guess this is intentional.

So this is not ‘fixed’ in Iron.

Yes, this is the behavior, I’ve wanted to address.

So my question is: What about giving the option to skip the field with default value, while building message with builder API?

This might increase backwards compatibility for the code, that uses builder API.

If you have a node that utilizes builder API, and you want to add a field to a message, used in this node, you might add a field with default value. In this case, you will still need to rebuild the node excutable, but you wouldn’t need to change the code of the aforementioned node.

That’s the general idea, but I’m not sure, if this is inline with the ros_idl’s design philosophy. If you think that this behavior is intentional, I guess it makes my proposal inconsistent with the design.