0

How can I make a wrapper class for existing object, which automatically prohibits or allows modification of wrapped object's data depending if wrapped object was provided to constructor with or without const type qualifier.

So if wrapper class received (WrappedObj *ptr), then it allows set and get methods. If wrapper class received (const WrappedObj *ptr), then only get methods are allowed at compile time.

Problem example:

I have a pointer to buffer where there is Ethernet header and I want to ease access to Ethernet data for readability and reduce mistakes by endianess.

struct ethhdr {
    uint8_t    h_dst[6];    /* destination eth addr */
    uint8_t    h_src[6];    /* source ether addr    */
    uint16_t   h_proto;     /* packet type ID field */
    uint8_t    h_data[0];
} __attribute__((packed));

// My wrapper "view" class, which doesn't really work as expected
class EtherView {
public:
    EtherView(uint8_t *ptr) : mPtr{(ethhdr*)ptr} {}
    EtherView(const uint8_t *ptr) : mPtr{(ethhdr*)ptr} {}

    /* SET METHODS */
    void setProtocol(uint16_t proto) {
        mPtr->h_proto = htons(proto);
    }
    /* ........ */

    /* CONST GET METHODS */
    uint16_t protocol() const {
        return ntohs(mPtr->h_proto);
    }
    /* ........ */

private:
    ethhdr *mPtr;
};

int main() {
    uint8_t fakeBuffer[128];
    EtherView ethView(fakeBuffer);
    //OK - we want to modify because fakeBuffer isn't const
    ethView.setProtocol(80); 

    const uint8_t *fakeConstBuff = fakeBuffer;
    EtherView constEthView(fakeConstBuff);
    // HERE I WANT COMPILE ERROR, because Wrapper was created with const param
    constEthView.setProtocol(80);

    /* I know its possible to define const wrapper:
    const EtherView constEthView(fakeConstBuff);
    , but I do not trust to "remember" to do it - it must be automatic. */

    return 0;
}

As you can see this wrapper gives safe and importantly fast wrapper for buffer modification/reading. I've tested and it has the same performance as if buffer value modifications were done inline (because they are inlined by compiler).

Possible (not perfect) solutions so far:

1) Remember to add const for wrapper class, when const data source.

  • Don't want to "remember" (will eventually make a mistake)

2) Make base class (EthViewConst) with getters and construct only on const source. Then inherit that class, which constructs on non-const source and has also setter functions. If there won't be any other solutions, then this probably will be the "best" way to do this, but:

  • Doesn't really meet requirements. I would like to have one class, but if not possible, then its acceptable.

3) Have some special Factory, which creates const or non-const object depending on data source. "Pseudo code" example: [EtherView view = EtherView::Create(buffer);], but I can't seem to find a way to do it, because:

  • returning const or non-const dynamically created Wrapper class pointer would work, but doesn't follow requirements: It must be fast. In this case it would be ralitively big cost.
  • returning const/non-const object doesn't work, because "receiver" can define non-const object and constantness will be lost by copy construction
  • returning reference can't be done for non-const object (becuase of temporary). Maybe there is some hacky way?

4) ..... any other solutions.....??

  • move construction?
  • perfect forwarding?
  • templates?
  • constexpr?
  • preprocessor - no thanks
Janis Coders
  • 157
  • 1
  • 6
  • if you expose setters to public then all attempts to maintain const becomes useless. you can try make all fields in the struct const and only allow and implement all necessary constructors to make sure the struct can be filled on initialization. – user3528438 Feb 15 '16 at 22:38
  • I don't really understand what you are after, why can't you just remove all the setter functions? – Galik Feb 15 '16 at 22:43
  • 1
    Some readings for what you are essentially trying to do. These indicate that your option 2 is the best approach, and you'll just have to deal with it: (A) https://bytes.com/topic/c/answers/637500-making-constructor-const (B) http://stackoverflow.com/questions/6936124/why-does-c-not-have-a-const-constructor (C) https://www.reddit.com/r/cpp/comments/2yei1n/the_c_language_standard_and_const_constructors/ – paddy Feb 15 '16 at 23:11
  • If on wrapper class construction the passed parameter is const, then I want that compiler allows to use ONLY getters, otherwise setters and getters. – Janis Coders Feb 15 '16 at 23:12
  • I edited the question to be more specific. Can it be re-opened? – Janis Coders Feb 20 '16 at 13:15

1 Answers1

1

This is probably complete abuse of the template mechanism, and your mileage will vary depending on if unused template methods are generated, and if they are, how your linker deals with them. I will say that the following is most likely not portable and probably insane. Luckily, you can hide behind some type aliases for safety if it doesn't port well.

This method complains extremely loudly if you use a const uint16_t* in the mutable version of the view, or a uint16_t* in the const version of the view.

#include <type_traits>

struct ethhdr {
    uint8_t    h_dst[6];    /* destination eth addr */
    uint8_t    h_src[6];    /* source ether addr    */
    uint16_t   h_proto;     /* packet type ID field */
    uint8_t    h_data[0];
} __attribute__((packed));


template<typename T, typename U>
class EtherView {
public:
    EtherView(T* ptr) : mPtr{(U*)ptr} {}

    /* SET METHODS */
    void setProtocol(T proto) {
        mPtr->h_proto = proto;
    }

    /* CONST GET METHODS */
    T protocol() const {
        return mPtr->h_proto;
    }
private:
    U *mPtr;
};

using MutableEtherView = EtherView<uint16_t, ethhdr>;
using ConstEtherView = EtherView<const uint16_t, const ethhdr>;

int main() {
    uint8_t fakeBuffer[128];
    MutableEtherView ethView(fakeBuffer);
    ethView.setProtocol(80);

    const uint8_t *fakeConstBuff = fakeBuffer;
    ConstEtherView constEthView(fakeConstBuff);
    MutableEtherView mutView(fakeConstBuff);        // Generates error here
    constEthView.setProtocol(80);                   // Generates error here

    return 0;
}
pyj
  • 1,489
  • 11
  • 19