-2

The scenario: I want to read a file which contains value with a data type defined in its header, copy its content to a temporary image, modify the content of the temporary image and save it again.

The problem is that the size of the data type requires different ways of accessing/modifying the content and results in large switch statements due to different data types (the list here is shortened). Also the type is only known at runtime, not at compile-time.

#include <stdint.h>
#include <stdlib.h>

typedef enum {
    DT_UCHAR,                   /** @brief Datatype 'unsigned char'     */
    DT_CHAR,                    /** @brief Datatype 'signed char'       */
    DT_USHORT,                  /** @brief Datatype 'unsigned short'    */
    DT_SHORT,                   /** @brief Datatype 'signed short'      */
    DT_UINT,                    /** @brief Datatype 'unsigned int'      */
    DT_INT,                     /** @brief Datatype 'signed int'        */
    DT_FLOAT,                   /** @brief Datatype 'float'             */
    DT_DOUBLE,                  /** @brief Datatype 'double'            */
} e_datatype;

struct image {
    e_datatype      type;
    size_t          size;
    void*           data;
};

image image1;
image image2;
image image3;

template <typename T>
void create_mask(void *const dst, const unsigned i_dst, const void *const src, const unsigned i_src, const void* const threshold) {
    if (static_cast<const T*>(src) < static_cast<const T*>(threshold))
        *(static_cast<T*>(dst)+i_dst) = 1;
}


void create_mask(image *const out, const unsigned out_i, const image *const in, const unsigned in_i, const image* const threshold) {

    if (in->type != threshold->type || out->type != DT_UCHAR)
        return;

    switch (out->type) {
    case DT_UCHAR:
        create_mask<unsigned char>(out->data, out_i, in->data, in_i, threshold->data);
        break;
    case DT_CHAR:
        create_mask<signed char>(out->data, out_i, in->data, in_i, threshold->data);
        break;
    case DT_USHORT:
        create_mask<unsigned short>(out->data, out_i, in->data, in_i, threshold->data);
        break;
    case DT_SHORT:
        create_mask<signed short>(out->data, out_i, in->data, in_i, threshold->data);
        break;
    case DT_UINT:
        create_mask<unsigned int>(out->data, out_i, in->data, in_i, threshold->data);
        break;
    case DT_INT:
        create_mask<signed int>(out->data, out_i, in->data, in_i, threshold->data);
        break;
    case DT_FLOAT:
        create_mask<float>(out->data, out_i, in->data, in_i, threshold->data);
        break;
    case DT_DOUBLE:
        create_mask<double>(out->data, out_i, in->data, in_i, threshold->data);
        break;
    default:
        //printf("ERROR. Unknown type.\n");
        break;
    }
}

size_t sizeof_image(e_datatype type) { return 1 /* another switch with the size of each datatype */;  }

void read_image() {

    image *my_image1 = &image1;
    image *my_image2 = &image2;
    image *threshold = &image3;

    // read header and save it in my_image and then
    // read data and copy it to the data field of my_image
    read_image_header("mydata_uint.dat", my_image1);
    my_image1->data = calloc(my_image1->size, sizeof_image(my_image1->type));
    read_image_data("mydata_uint.dat", my_image1);

    // create output mask
    my_image2->size = my_image1->size;
    my_image2->type = DT_UCHAR;
    my_image2->data = calloc(my_image2->size, sizeof_image(DT_UCHAR));

    // read threshold value from another image
    read_image_header("mydata_thresh.dat", threshold);
    threshold->data = calloc(threshold->size, sizeof_image(threshold->type));
    read_image_data("mydata_thresh.dat", threshold);

    for (unsigned i = 0; i < my_image1->size; i++)
        create_mask(my_image1, i, my_image2, i, threshold);
}

Is it possible to rewrite the image class/struct such that it would use a template class with the datatype being set at inside read_image()? Hence reducing the amount of switch statements.

My limitation is that I cannot use C++ Standard Library features and I am limited to C++03.

I found a solution with function pointers but this solution does not seem shorter than those large switch statements.

Community
  • 1
  • 1
Christian
  • 3
  • 3
  • ... and can you use virtual functions ? – quantdev Jan 13 '15 at 16:55
  • If you make `image` a template (without that ugly `void*`) you may improve your overall design. – stefan Jan 13 '15 at 16:59
  • I think so. How does this avoid the fact that I only know the datatype at runtime? – Christian Jan 13 '15 at 16:59
  • Use `memcmpy` instead. – Brandon Jan 13 '15 at 17:00
  • I want to get rid of the `void*` and make the `image` a template but I couldn't come up with a working solution. – Christian Jan 13 '15 at 17:00
  • `copyValue` is just an example of one operation, there are many more. Hence using `memcmpy` is not an option. – Christian Jan 13 '15 at 17:02
  • 1
    Are you looking for shorter or are you looking for *efficient*? A function table may be worth benching. If you're looking for runtime-data to find code to execute, your options are somewhat limited. You gotta test the data *somehow*. – WhozCraig Jan 13 '15 at 17:11
  • Relevant/Possible Duplicate Of: http://stackoverflow.com/questions/23092121/replaceing-switch-statements-when-interfaceing-between-templated-and-non-templat – IdeaHat Jan 13 '15 at 17:19
  • C++03, I guess I understand, but why no standard library ? Are you compiling on a coffee pot or something ? – Quentin Jan 13 '15 at 17:47
  • Could you should us example usage of `copy1D()`? Also, you mentioned it `is just an example of one operation`, what others do you need? – Julian Jan 13 '15 at 19:43
  • There are basics mathematical operations between two "image" (adding, divding, incrementing, ...) and some more advanced versions which compare values between two images and set the output into a third one of different datatype (creating a mask). The coffee pot is not that far off actually; the STL is a no-go :) I hoped for both a shorter and possibly more efficient version by eliminating the `void*` and using classes. – Christian Jan 14 '15 at 11:08
  • I changed the example to make it closer to the original code. – Christian Jan 14 '15 at 11:42

6 Answers6

2

Without knowing more about the use cases for copy1D() and your other functions, it's difficult to provide a complete solution. In any case, a possible alternative to your solution would be to use dynamic polymorphism. For example:

Create an interface with all of methods you need

// Interface
struct Image {
    virtual Image* copy() = 0;
    virtual void manipulate() = 0;
    virtual void writeToFile() = 0;
    virtual ~Image() { }
};

Now let templates take care of the implementation of your interface

// Templated implementation
template <typename T>
struct ImageImpl : Image {

    size_t size;
    T* data;

    ImageImpl( const ImageImpl<T>& other ) {
        size = other.size;
        data = new T[size];
        memcpy( data, other.data, size * sizeof(T) );
    }

    ~ImageImpl() {
        delete[] data;
    }

    virtual Image* copy() {
        return new ImageImpl( *this );
    }

    virtual void manipulate() {
        for ( int i = 0; i < size; i++ ) {
            data[i] = data[i] + 1; // Do something with the data
        }
    }

    virtual void writeToFile( const char* filename ) {
        for ( int i = 0; i < size; i++ ) {
            write( data[i] );
        }
    }
};

Example usage:

Image* newFromFile( const char* filename )
{
    Image* i = NULL;

    if ( isIntFile( filename ) ) {
        i = new ImageImpl<int>( ... );
    }
    else if ( isFloatFile( filename ) ) {
        i = new ImageImpl<float>( ... );
    }
    ...

    return i;
}

int main()
{
    Image* i = newFromFile( "example.img" );

    Image* iCopy = i->copy();
    iCopy->manipulate();
    iCopy->writeToFile( "new.img" );

    delete iCopy;
    delete i;
}
Julian
  • 1,688
  • 1
  • 12
  • 19
  • I think that is *precisely* what I was looking for, thanks a lot! I have to see if I can pass find a way to pass template-dependent arguments to the `manipulate()` method. – Christian Jan 14 '15 at 11:13
0

Your problem is a classic one of "data being used to find code". There is no way around it other than to provide code for every valid pathway (i.e. every valid data type plus one error pathway).

Further template magic won't in the end reduce keystrokes. Macros might.

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
0

Overloaded functions, based on type, might help.

Also, a map of data type name (or ID) and function pointers could also help. This works in the C language.

Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
0

Since your enum is contiguous, you don't need a fancy map and can use an old-fashioned array (if you can live without error-checking).

typedef void (*copyFunction)(void *const, const unsigned, const void *const, const unsigned);

void copy1D(image *const out, const unsigned out_i, const image *const in, const unsigned in_i)
{
    static const copyFunction functions[] =
    {
       copyValue<unsigned char>,
       copyValue<signed char>,
       copyValue<unsigned short>,
       copyValue<signed short>,
       copyValue<unsigned int>,
       copyValue<signed int>,
       copyValue<float>,
       copyValue<double>
    };
    functions[out->type](out->data, out_i, in->data, in_i);
}

You can generalise this using a macro:

#define MAKE_TABLE(function)\
   {\
       function<unsigned char>,\
       function<signed char>,\
       function<unsigned short>,\
       function<signed short>,\
       function<unsigned int>,\
       function<signed int>,\
       function<float>,\
       function<double>\
   }

typedef void (*copyFunction)(void *const, const unsigned, const void *const, const unsigned);

void copy1D(image *const out, const unsigned out_i, const image *const in, const unsigned in_i) 
{
   static const copyFunction functions[] = MAKE_TABLE(copyValue);
   functions[out->type](out->data, out_i, in->data, in_i);
}

Or, you can have the function pointer as a member of image (this is like a manually managed virtual function)

typedef void (*copyFunction)(void *const, const unsigned, const void *const, const unsigned);

struct image {
   e_datatype      type;
   size_t          size;
   void*           data;
   copyFunction    copy;
};

void read_image() 
{
    image *my_image = 0;
    // Read image...
    // ...

    // Set up the "virtual function"
    static const copyFunction functions[] = MAKE_TABLE(copyValue);
    my_image->copy = functions[my_image->type];
}

void copy1D(image *const out, const unsigned out_i, const image *const in, const unsigned in_i) 
{
   out->copy(out->data, out_i, in->data, in_i);
}
molbdnilo
  • 64,751
  • 3
  • 43
  • 82
  • I haven't thought about that approach, it sounds pretty straight forward and probably the only realisable solution. – Christian Jan 14 '15 at 11:06
0

It's not clear from your code snippet what are the actual differences in processing of different data-types (other than sizeof-related).

One of C++ ways to handle a family of "typed" objects is to define traits in conjunction with a common object template (should be ok with C++03), then implement the type-specific handling in template-specializations with given "trait". Essentially, traits takes care of "enum". This may indeed compact the written code quite a bit, especially when handling for different data-types is much similar.

HOWEVER, in your case the decision about the data-type seems to be tied to the external data (file). Your data-type granularity is quite detailed (i.e. you define short, unsigned types), so a simple strtol/strtod() won't cut here to produce a templated object directly.

Thus you may want to include the data-type-ID in your external data and use it to create a traits object that in its turn would yield you a templated data-object which actually consumes the data. Otherwise you'd need to have at least one swtich statement after parsing the external data to map your external (string-based) type to internal (data-type-ID) type.

I find a good intro on traits is by Alexandrescu: http://erdani.com/publications/traits.html.

vmsnomad
  • 319
  • 2
  • 7
  • The difference is most of the time only sizeof-related (except for some half-float datatype I did not mention here). I tried to clean up a lot of the code with traits but in the above case I didn't find a proper way of doing so. – Christian Jan 14 '15 at 11:03
0

You are already going into the right direction by separating image IO from the image itself. In order to be able to use the type of the image in the writer you have multiple options. My approach would be:

#include <cstdint>
#include <vector>

using namespace std;

template <typename T> class Image
{
public:
    typedef T Pixel_t;
    vector<Pixel_t> data; // or any suitable data structure

// all other stuff that belongs to the Image Class
};

template <class T> ImageWriter
{
public:
    typedef T Image_t;

    void writeImage(const Image_t& image)
    {
        write(file, &image[0], image.size()*sizeof(Image_t::Pixel_t));
    }
// other stuff that belongs to the ImageWriter
private:
    void write(File& file, void* data, size_t size);
};

You can then use that in you application

typedef Image<uint32_t> img_t;

void myStuff()
{
    img_t img;
    ImageWriter<img_t::Pixel_t> writer;

    // ...
    writer.writeImage(img);
}
ogni42
  • 1,232
  • 7
  • 11
  • I forgot to mention that I have global variables of type "image" which I want to re-use for different image data types. I changed the example to reflect that. – Christian Jan 14 '15 at 11:43
  • This would mean, that you change the type of the image which will lead to every other method that you call have to check for the concrete type of the image, throwing you back to square one. Instead, use the Image<>::Pixel_t to templatize your other methods, e.g. the filter methods – ogni42 Jan 14 '15 at 14:15