ZioByte
ZioByte

Reputation: 3004

C++: can I get a "generic" (non-template) pointer (or reference) to a template class?

I have the following problem:

Something along the lines:

#include <cstddef>
#include <string>
#include <map>

template <class T> class block {
private:
    T           *ptr;
    std::string name;
    std::size_t size;

    static std::map<std::string, block<T>&>    blocks;
public:
    block(std::string _name, T *_ptr, std::size_t _size)
    : name(_name)
    , ptr(_ptr)
    , size(_size)
    {
        blocks[_name] = this;
    }
    
    virtual ~block() {}

    static block<T> &find(std::string key) {
        return blocks[key];
    }
};

Now I have the following problem:

write a function that, given the block name, an index and a unsigned char [], should fill a single element element in the right block taking as many bytes "as needed".

Signature of such a function (or member) should be something like:

void set_element(std::string block_name, int index, void *source);

Is there a clean way to do this?

Ideally the only prerequisite should be there actually are enough bytes in the pointed array (I can add a std::size_t available_bytes to function signature to actually check and throw an error if there aren't enough bytes available).

If useful: reason why I need this is the I receive "unstructured" data from network and that's a byte stream I need to parse piece by piece.

To be more precise: the input stream i get is composed by a sequence of:

There may be multiple "blocks" stick back-to-back in the input stream so it would be useful to have a return value stating either the number of bytes used or a pointer to first unused.

It's assumed binary data it comes from a compatible architecture and thus it is a correct representation.

The block name implicitly gives the size and characteristics of the element.

Incoming data is not supposed to be aligned; OTOH array pointed in block::ptr is supposed to be correctly aligned so I can access it with normal pointer arithmetic.

Note: the above example code is not good because it will produce separate blocks for different template arguments while I need all names to be thrown in the same bag; I assume I will need to implement a parent class, but it's unclear exactly how to do it: I will update if I come to it.

Upvotes: 2

Views: 84

Answers (1)

ZioByte
ZioByte

Reputation: 3004

I went through the exercise of deriving from a base class.

Here is a piece of code actually compiling:

#ifndef AWBLOCK_H_
#define AWBLOCK_H_

#include <stdexcept>
#include <cstddef>
#include <string>
#include <map>

namespace AW {

class generic_block {
private:
    static std::map<std::string, generic_block*>    blocks;

protected:
    std::string name;
    std::size_t size;

    virtual int _set(int, void *) = 0;

public:
    generic_block(std::string _name, std::size_t _size)
    : name(_name)
    , size(_size)
    {
        blocks[_name] = this;
    }

    virtual ~generic_block() {}

    static generic_block *find(std::string key) {
        return blocks[key];
    }

    void *set(std::string _name, int _index, void *data) {
        if (blocks.find(_name) == blocks.end()) {
            throw std::invalid_argument("Unknown block '"+_name+"'");
        }
        if (_index >= (int)size) {
            throw std::out_of_range("Index "+std::to_string(_index)+" exceeds size of block '"+_name+"' ("+std::to_string(size)+")");
        }
        int n = blocks[_name]->_set(_index, data);
        return ((char *)data)+n;
    }

    virtual int count(void) {
        return size;
    }
};

template <class T> class AWblock: public generic_block {
private:
    T           *ptr;

public:
    AWblock(std::string _name, T *_ptr, std::size_t _size)
    : generic_block(_name, _size)
    , ptr(_ptr)
    {
    }

    virtual ~AWblock() {}

    int _set(int _index, void *_data) {
        int n = sizeof(*ptr);
        memcpy(ptr+_index, _data, n);
        return n;
    }
};

} /* namespace AW */

#endif /* AWBLOCK_H_ */

Minimal test program:

#include "gtest/gtest.h"

#include "AWblock.h"

#define countof(__) (sizeof(__)/sizeof(__[0]))

namespace AW {

std::map<std::string, generic_block*>    generic_block::blocks;

int int_block[22];
AWblock<int32_t> _int_block("int-block", int_block, countof(int_block));

TEST(AWblock, HandleCount) {
    EXPECT_EQ(_int_block.count(), 22);
}

} /* namespace AW */

apparently works fine:

Running main() from /usr/src/googletest/googletest/src/gtest_main.cc
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from AWblock
[ RUN      ] AWblock.HandleCount
[       OK ] AWblock.HandleCount (0 ms)
[----------] 1 test from AWblock (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 1 test.

If there's a better approach I will gladly accept it, otherwise I will accept my own answer in a few days.

Upvotes: 1

Related Questions