Implicit conversions to/from library type only in implementation

1. I designed two classes: Processor and Data.
2. Data represents some data object the user can pass to Processor's public methods.
3. Internally, Processor needs to use InternalData type which is based on the content of Data (I can use Data's public interface to get the required information from it, or construct a Data object using its public constructor when needed, and that's how I have done it so far).
4. To avoid repeating code and localize changes required when Data's interface would change someday, I made conversion functions from Data to InternalData and back inside Processor, as private methods.

Now here comes the kicker:
5. But I'd like these conversions to be implicit inside Processor's methods instead of explicit. And only there.
6. These conversion functions are only for Processor implementation's use. They shouldn't be visible nor accessible from the outside world.

Where the problem lays:
7. InternalData is a library type. I don't have control over it and I cannot modify its interface.
That is, I cannot just add converting constructors or conversion operator member functions to them.
You can consider it to be built-in type if you wish.
8. I don't want to put those converters inside Data class either, since it's not its business and it shouldn't know that Processor converts it to something else internally.

Long story short, I'd like to teach the Processor's implementation how to make type conversions between Data and InternalData implicitly, but no one else except Processor should be affected by it. Outside world shouldn't be able to do these conversions or even know about them being done inside Processor's implementation.

Is there any way to do it in C++?

The core of the problem seems to be the fact that in C++ defining implicit conversions is possible only from/to a user-defined type when defining this type. I don't know of any way to define such conversions for some other type's internal use only. (Especially when I don't have control about one of these converted types.)
Last edited on
As far as I know, no. The only way of doing it is to have the conversion between InternalData and Data be done manually. However, wouldn't it be easier to store a variable of type InternalData and set that from an object of type Data, and then use the InternalData within Processor as normal? And then whenever its required to retrieve or export the data then a conversion to type Data can be performed?
Last edited on
If there is no other way, I can revert to storing InternalData within Data's privates, as you've said. That's how it's been done before. But then I need to keep it hidden from the user of Data, because InternalData is an implementation detail which shouldn't be known to users (especially that this implementation can change).
So the same goes to these conversions: they can be defined inside Data, but they need to be hidden somehow, only for the use of Processor's implementation.

My first attempt was to store InternalData in Data as private, put these conversions there (also private), and make Processor a friend to be able to access these converters. But then Processor also has access to all internals of Data, which is not what I want. I would rather limit access to those converters.

This has also the drawback that Data needs to jump through hoops for Processor implementation's sake, while it shouldn't care about what Processor is doing with it internally.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
struct Data { /* ... */ };

struct InternalData { /* ... */ };

struct processor
{
    void foo( Data& d )
    {
        internal_data_ id = d ; // convert Data to internal_data_
        // use id

        bar(d) ; // convert Data to internal_data_
    }

    private:
        
        // internal_data_ can be used exactly like InternalData
        struct internal_data_ : public InternalData 
        {
            using InternalData::InternalData ;

            internal_data_( const Data& d ) : InternalData( data_to_internal_data(d) ) {}

            static InternalData data_to_internal_data( const Data& )
            {

                std::cout << "convert Data to InternalData\n" ;
                // ...
                return InternalData{ /* .... */ } ;
            }
        };

        void bar( const internal_data_& id ) { /* ... */ }
};
Wow! This:
1
2
3
4
private:
        
        // internal_data_ can be used exactly like InternalData
        struct internal_data_ : public InternalData


is ingenious :-> Thanks, I'll try to go with this idea and check how far I can go with it.

Side question: Would it also work if InternalData were a built-in type? I see a problem coming with this deriving from InternalData. (Luckily in my case this is a struct used by a system library).
OK this technique seem to work, but it has some limitations.

First off, the library functions know nothing about my internal_data_. They continue to request InternalData, which is a problem when they require pointers to this data structure, which they then write over. Passing a pointer to some other derived structure might be dangerous, I guess, if the layout in memory would be different from some reason. There could be slicing problems, too.

Second, the library which defines InternalData also defines some callback functions which accept pointers to InternalData. There's a library function which allows to set an address of such callback defined by the user (that is, my implementation in this case), and the type of the pointer to that function is in a form void (*callbackType)(InternalData* idata);. I cannot define a callback which accepts the internal_data_ wrapper, because then it wouldn't be accepted by the system callback setter (type mismatch). Pointer to function accepting my wrapper is not the same as the function accepting the original InternalData. So I need to use the original callback setter as is, and leave the callback function signature as is, but convert to internal_data_ explicitly (constructing).

Similarly I have a std::vector<Data> stored inside Processor. It cannot be accessed directly, but there is a const std::vector<Data>& getter which allows users of Processor to look through it (but not modify). To get it going, I'd have to switch to std::vector<internal_data_> internally. This will work when putting data into that vector internally, since it would convert InternalData to internal_data_ implicitly.
But the problem pops out somewhere else:
Now the user knows about internal_data_ which is an implementation detail! He should see this vector as std::vector<Data>, not std::vector<internal_data_>. Those are different types for the compiler, despite the fact their elements are convertible to each other. Keeping two separate vectors would make it look clean for the user, but will make a horrible mess and maintainability problems in the implementation, I guess ;-/

And all this stuff seems to be more complex than just using the explicit conversion functions, which I seem to being forced using anyway. So I probably stick with the old solution nevertheless.

It is quite a pity, though, that such things cannot be easily done in C++ natively.
Last edited on
> Passing a pointer to some other derived structure might be dangerous, I guess,
> if the layout in memory would be different from some reason.

The layout of the base-class sub-object (InternalData) would not be affected.


> There could be slicing problems, too.

Not when we pass a pointer, or pass the object by reference.


> pointer to that function is in a form void (*callbackType)(InternalData* idata);.
> I cannot define a callback which accepts the internal_data_ wrapper

We only need to define a a callback which accepts a pointer to InternalData. (There is an implicit conversion).


> I have a std::vector<Data> stored inside Processor.
> there is a const std::vector<Data>& getter which allows users of Processor to look through it.
> To get it going, I'd have to switch to std::vector<internal_data_> internally.
> He should see this vector as std::vector<Data>, not std::vector<internal_data_>.

This is not a problem that is newly created because of the implicit conversion. Even if we keep std::vector<InternalData> with explicit conversions, a const std::vector<Data>& getter is not feasible.

One possibility is to keep std::vector<Data> and implicitly convert on the fly as required.

Another is to keep std::vector<internal_data_> or std::vector<InternalData> and make the getter return a std::vector<Data> by value.
Topic archived. No new replies allowed.