Immediately invoked lambda
One alternative that connects data member names with the value to be changes are using an immediately invoked lambda:
auto sd2 = [](auto s) { s.a = 12; s.b = 'ten'; return s; }(sd1);
A macro could potentially be used to make this into a simpler form.
Variadics and pointer-to-members
Another approach that I was hoping would end up less clunky (but didn't) are to use variadic auto non-type template parameters with pointer-to-members as arguments, to specify which members of a modified copy that you would like to replace along with their new values:
namespace detail {
// Helper trait to get underlying type of member from pointer-to-member.
template <typename T> struct ptr_to_member_underlying;
template <typename ClassType, typename MemberType>
struct ptr_to_member_underlying<MemberType ClassType::*> {
using type = MemberType;
};
template <typename T>
using ptr_to_member_underlying_t = typename ptr_to_member_underlying<T>::type;
template <typename T, auto mem_ref, typename V>
void assignMember(T &t, V &&mem_value) {
t.*mem_ref = std::forward<V>(mem_value);
}
} // namespace detail
/// @tparam mem_refs Pointer-to-members for the public data members of
/// `t`that should be modified in the copy.
/// @param t Object to be copied.
/// @param new_mem_values New values for the modified data members of `t`.
template <auto... mem_refs, typename T>
T modifiedCopy(T const &t,
detail::ptr_to_member_underlying_t<decltype(mem_refs)>
&&... new_mem_values) {
auto tc = t;
(detail::assignMember<T, mem_refs>(
tc, std::forward<detail::ptr_to_member_underlying_t<decltype(mem_refs)>>(
new_mem_values)),
...);
return tc;
}
int main() {
SomeData sd1{.a = 10, .b = "10"};
auto sd2 = modifiedCopy<&SomeData::b>(sd1, "ten");
}
This also emphasizes "specify the members you want to change" semantics as opposed to the one you list which have "specify the members you want to keep and change" semantics. This differentiation could help when structs are becoming bigger, in case they are generally more similar than dissimilar (still the concern of separating member reference to the value that should be assigned to it).
You could use a macro to make this less clunky, however with the general concerns of using macros.
Useful third party libs?
boost_pfr could be used to create a briefer (tuple-like referencing) modifiedCopy API that takes data member indices as non-type template parameters instead of pointers-to-members:
auto sd2 = modifiedCopy<1>(sd1, "ten");
// ^-- modify member 1 (2nd member; 0-based indexing) to "ten"