0

I want my code to be clean and therefore I would like to achieve something like this:

Set<Color>(255, 255, 255);
Set<Opacity>(128);
Draw<Rectangle>(0, 0, 50, 50);
Set<Opacity>(255);
Draw<Texture>(Textures::Woman, 75, 75);

Basically multiple forms of a function for different names that are placed between the <>.

Would be great if it wasn't an actual template though, that would require me to code everything in .h files and I don't find that clean.

Szwagi
  • 21
  • 3
  • 3
    You can not use the `<>` syntax without templates, so its impossible to use this without templates. Note you can use explicit template instantiations so you don't need all code in your header file. – tobspr May 02 '16 at 15:54
  • you can do the trick of implementing the template body in a .cpp file and then include it into the header – David Haim May 02 '16 at 15:58
  • What do you actually want to solve with this technique? Using mixin bases for property implementations? You should elaborate about your use case and show a complete (pseudo) code sample please. – πάντα ῥεῖ May 02 '16 at 16:00
  • It doesn't save the users of your API any typing, so why? – StoryTeller - Unslander Monica May 02 '16 at 16:01

5 Answers5

4

How about using function overloads.

Set(Color(255, 255, 255));
Set(Opacity(128));
Draw(Rectangle(0, 0, 50, 50));
Set(Opacity(255));
Draw(Texture(Textures::Woman, 75, 75));
Martin York
  • 257,169
  • 86
  • 333
  • 562
1

Why not just overload the functions (assuming that the signature is different every time, which they are in your case)?

//set color
void Set(int r, int g, int b) {}

//set opacity
void Set(int op) {}

//draw rectangle
void Draw(int x, int y, int w, int h) {}

//draw image
void Draw(Texture tex, int w, int h) {}

Note: Making individual functions (SetColor, SetOpacity) would make the code much clearer than those overloads which have nothing to do with each other, so you should consider using that instead.

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
  • The drawback with the example code is, that such an interface does not give any semantic information on the caller side. What exactly does `Set(42, 17, 32);` set? – cdonat May 02 '16 at 16:35
  • @cdonat true, you could however interpret the arguments (`r`, `g` and `b` -> that has to be a color). But I agree, this is not the best way. – Rakete1111 May 02 '16 at 16:37
  • At the caller side, you don't see the parameter names, when you read the code. – cdonat May 02 '16 at 16:41
  • @cdonat only if you're using a good enough IDE, but if you are not using one, stick with Loki's answer or my alternative. – Rakete1111 May 02 '16 at 16:43
0

Another way is to use tags:

//set color
void Set(Color_tag, int r, int g, int b) {}

//set opacity
void Set(Opacity_tag, int op) {}

//draw rectangle
void Draw(Rgectangle_tag, int x, int y, int w, int h) {}

//draw image
void Draw(Texture_tag, Texture tex, int w, int h) {}

In order this to work, you will need also:

struct Color_tag{};
struct Opacity_tag{};
struct Rectangle_tag{};
struct Texture_tag{};

Color_tag color;
Opacity_tag opacity;
Rectangle_tag rectangle;
Texture_tag texture;

then your code will look like:

Set(color, 255, 255, 255);
Set(opacity, 128);
Draw(rectangle, 0, 0, 50, 50);
Set(opacity, 255);
Draw(texture, Textures::Woman, 75, 75);

This way of working is very powerful, it will allow to have something like this:

//set color
void Set(Color_tag, int r, int g, int b) {}

//set Hex color (HTML style)
void Set(Color_tag, int color) {}

//set opacity, same parameters like set hex color
void Set(Opacity_tag, int op) {}

However I like more normal overloads similar to what Blitz Rakete suggested.

Nick
  • 9,962
  • 4
  • 42
  • 80
0

What's unclean about creating the methods SetColor(int,int,int), SetOpacity(int), etc? If you want simplicity in your headers you could write macros to streamline get/set creation.

e.g.

#define SYNTHESIZE(TYPE, VARNAME, FUNCNAME) \
protected: TYPE VARNAME;\
public: virtual TYPE get##FUNCNAME(void) const { return VARNAME; }\
public: virtual void set##FUNCNAME(TYPE var){ VARNAME = var; }

Use

class Foo
{
   SYNTHESIZE(float, bar, Bar);
}

As @tobspr says you can't use the < > syntax without using templates or modifying the compiler. There are however ways to specialise a template so you can define it in a cpp instead.

See: Explicit instantiation - when is it used?

Community
  • 1
  • 1
0

Instead of a template parameter, have a single parameter that encodes the specific kind of action in its type.

class Color {
    private:
        uint8_t r_;
        uint8_t g_;
        uint8_t b_;
    public:
        Color(uint8_t r, uint8_t g, uint8_t b):
            r_{r}, g_{g}, b_{b} {};
};

class Opacity {
    private:
        uint8_t alpha_;
    public:
        Opacity(uint_8 alpha): alpha_{alpha} {};
};

class Rectangle {
    private:
        uint32_t x_;
        uint32_t y_;
        uint32_t w_;
        uint32_t h_;
    public:
        Rectangle(uint32_t x, uint32_t y, uint32_t w, uint32_t h):
            x_{x}, y_{y}, w_{w}, h_{h} {};
};

// etc.

And then use a function overload instead of a template.

class Context {
    public:
        Set(const Color& c);
        Set(const Opacity& o);
        Draw(const Rectangle& rect);
        // etc.
}

The usage is at least very close to what you try to achieve.

Set(Color{255, 255, 255});
Set(Opacity{128});
Draw(Rectangle{0, 0, 50, 50});
Set(Opacity{255});
Draw(Texture{Textures::Woman, 75, 75});

You also might like to have different Rectlangle constructors, one with two points and one with a point and width and height. There you can use the same strategy:

class Width {
    private:
        uint32_t w_
    public:
        Width(uint32_t w): w_{w} {};
        operator uint32_t () { return w_; };
};
class Height {
    // same as Width
};

class Rectangle {
    private:
        uint32_t x1_;
        uint32_t y1_;
        uint32_t x2_;
        uint32_t y2_;
    public:
        Rectangle(uint32_t x, uint32_t y, Width w, Height h):
            x1_{x}, y1_{y}, x2_{x+w}, y2_{y+h} {};
        Rectangle(uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2):
            x1_{x1}, y1_{y1}, x2_{x2}, y2_{y2} {};
};

// usage
Draw(Rectangle{0, 0, Width{50}, Height{50}}); // use width and height
Draw(Rectangle{0, 0, 50, 50}); // use two points

You can even go further and define a Point type, so that drawing a rectangle will look like this:

Draw(Rectangle{Point{0, 0}, Width{50}, Height{50}}); // use width and height
Draw(Rectangle{Point{0, 0}, Point{50, 50}}); // use two points

Use techniques like that to find the perfect balance between stating of intent and reducing noise.

And of course you can add simple template functions to your Context:

template<typename T, typename A...>
Set(A&& args...) {
    Set(T{std::forward(args)...});
}

template<typename T, typename A...>
Draw(A&& args...) {
    Draw(T{std::forward(args)...});
}

Now you can use the whole machine as you originally intended.

Set<Color>(255, 255, 255);
Set<Opacity>(128);
Draw<Rectangle>(0, 0, 50, 50);
Set<Opacity>(255);
Draw<Texture>(Textures::Woman, 75, 75);

// With width and height types
Draw<Rectangle>(0, 0, Width{50}, Height{50});
Draw<Rectangle>(0, 0, 50, 50);
// With Point Type
Draw<Rectangle>(Point{0, 0}, Width{50}, Height{50});
Draw<Rectangle>(Point{0, 0}, Point{50, 50});
cdonat
  • 2,748
  • 16
  • 24