How to evenly implement two-way conversion without repeating code?

I have two large C structures used in legacy C code, and I need to convert from one to the other, and vice versa. Something like that:

#include <iostream> struct A { int a; float b; }; struct B { char a; int b; }; struct C { A a; B b; }; struct D { int a; char b; float c; }; void CtoD( const C& c, D &d ) { da = caa; db = cba; dc = cab; } void DtoC( const D &d, C& c ) { caa = da; cba = db; cab = dc; } int main() { C c = { { 1, 3.3f }, { 'a', 4 } }; D d = { 1, 'b', 5.5f }; #if 0 CtoD( c, d ); #else DtoC( d, c ); #endif std::cout<<"C="<<caa<<" "<<cab<<" "<<cba<<" "<<cbb<<std::endl; std::cout<<"D="<<da<<" "<<db<<" "<<dc<<std::endl; } 

The CtoD and DtoC do the same thing, but in the opposite direction. Changing one structure requires changing both of them.

To minimize the likelihood of error and avoid repetition, I would like to implement some kind of mapping where I define the connections only once and then copy one value to another. Thus, only when changing the structure requires only one change.

So the question is: how to do this? Is it possible to use a design template?


There are hundreds of fields in my real structures. The above example is simply simplified.

+6
source share
7 answers

Let me be naughty ...

 struct rightwards_t {} rightwards; struct leftwards_t {} leftwards; template<typename Left, typename Right> inline void map_field(Left& left, const Right& right, leftwards_t) { left = right; } template<typename Left, typename Right> inline void map_field(const Left& left, Right& right, rightwards_t) { right = left; } template<typename Direction> void convert(C& c, D& d, Direction direction) { map_field(caa, da, direction); map_field(cba, db, direction); map_field(cab, dc, direction); } // Usage C c; D d; convert(c, d, leftwards); // Converts d into c convert(c, d, rightwards); // Converts c into d 

I don’t really know if it works (no compiler), but I wanted to write it. If someone can help me do it right, please do.

+1
source

In your literal example, I don’t think it is worth the hassle. Just write tests to make sure your conversions work well.

In your real code, if your structures have “hundreds of fields,” your structures may be poorly designed. Perhaps they should consist of smaller objects. I never designed anything that would require field regions in exactly the same structural object — instead, these fields allowed some sort of classification so that they could be processed in smaller bundles.

Since your code is outdated and you do not want to rewrite it, just write tests for your conversion functions, as I said above for an example.

Well-tested code is no longer obsolete code. Outdated code is basically code for which you do not have automated tests.

If rewriting is not an option, testing should be required.

Everything is said about the cost of testing "in both directions", comments of Idan Arye :

Since the transformation is symmetrical, testing in both directions is not much more work than testing it in one direction. All you have to do is install two structs - C c and D d - and install them as converted versions of each other. Then you just need to check that CtoD(c)==d and DtoC(d)==c (or use the comparison functions if you have them defined). A lot of work here is to initialize c and d - but you would have to do it anyway if you want to test a one-way conversion, so adding a test for another way is very cheap.

+2
source

You can do this with a container of hundreds of links std::pair links to related objects. Using links, you can read and write, so reading from the left object and writing to the right object are converted in one direction. The converse converts another path.

0
source

Choose your favorite scripting language (if you don’t already have one, I recommend Ruby) and write a small script that generates conversion functions (both source and header files) for you.

If you don’t choose a lame scripting language, you can even represent the connections directly in the language when calling the functions that the converters generate. For example, in Ruby, after defining generate_converters you can write:

 generate_converters :C,:D do convert 'a.a','a' convert 'b.a','b' convert 'a.b','c' end 
0
source

I agree with Daniel, it’s not worth the hassle, but you can write a small application that generates code for you. You download an application with a description of the two structures and the bindings between the members of the structure, and the application generates C code, which is then compiled as usual.

Another alternative is to use pointers for the participants , but this can absorb even more developer time, so the cost is even less than the first option.

0
source

It took me a while to figure out how to do this. And I came out with the following solution:

 #include <iostream> #include <algorithm> #include <cstring> struct A { int a; float b; }; struct B { char a; int b; }; struct C { A a; B b; }; struct D { int a; char b; float c; }; template< typename T1, typename T2 > struct DataField { static inline void Update( const T1 & src, T2 & dst ) { dst = src; } static inline void Update( T1 & dst, const T2 & src ) { dst = src; } }; template<> struct DataField< const char*, char* > { static inline void Update( const char* src, char* dst ) { strcpy( dst, src ); } }; template<> struct DataField< char*, const char* > { static inline void Update( char* dst, const char* src ) { strcpy( dst, src ); } }; template< typename T1, typename T2, int N > struct DataField< T1[N], T2[N] > { static inline void Update( const T1 (&src)[N], T2 (&dst)[N] ) { std::copy_n( src, N, dst ); } static inline void Update( T1 (&dst)[N], const T1 (&src)[N] ) { std::copy_n( src, N, dst ); } }; template< typename T1, typename T2 > void UpdateDataField( T1 & src, T2 & dst ) { DataField< T1, T2 >::Update( src, dst ); } template< typename T1, typename T2 > void UpdateMappedDataFields( T1 & src, T2 & dst ) { UpdateDataField( src.aa, dst.a ); UpdateDataField( src.ab, dst.c ); UpdateDataField( src.ba, dst.b ); } void CtoD( const C& c, D &d ) { UpdateMappedDataFields( c, d ); } void DtoC( const D &d, C& c ) { UpdateMappedDataFields( c, d ); } int main() { C c = { { 1, 3.3f }, { 'a', 4 } }; D d = { 1, 'b', 5.5f }; #if 0 CtoD( c, d ); #else DtoC( d, c ); #endif std::cout<<"C="<<caa<<" "<<cab<<" "<<cba<<" "<<cbb<<std::endl; std::cout<<"D="<<da<<" "<<db<<" "<<dc<<std::endl; } 

All data field mappings are performed in the UpdateMappedDataFields function and only there.

What I don't like is that the UpdateMappedDataFields function is a template and the way it is implemented prevents auto-completion when using the IDE, since the types are not known.

However, I still would like to hear if there is a better way.

0
source

Like Idan and Dialecticus, you can also simply use the search and replace function of the editor: For example. write CtoD manually, copy the body to DtoC and - in eclipse - use

  Find: ^(.*)=(.*); Replace: $2=$1; 

to automatically change the left and right sides of each assignment in the body of DtoC .

Whether it is preferable to use more or less complex C ++ constructs depends on your specific code and requirements. In my opinion, the code is easier to read and maintain in this way, but, of course, nothing provides consistency between CtoD and DtoC after future changes (I would mention the procedure in the code comment).

0
source

Source: https://habr.com/ru/post/955967/


All Articles