0

I have two C++ structures, Rect which is a floating point rectangle and iRect which is the same, but with integers:

struct iRect {
    int X;
    int Y;
    int W;
    int H;
};

struct Rect {
    float X;
    float Y;
    float W;
    float H;
};

Currently I can construct these using the syntax Rect{1.2, 2.4, 4.2, 0.2};.

Now I want to make these implicitly convertible, so I create a new constructor for Rect:

Rect(iRect iR) {
    X = (float)iR.X;
    Y = (float)iR.Y;
    W = (float)iR.W;
    H = (float)iR.H;
}

And I get: error C2512: 'Rect' : no appropriate default constructor available No big deal, I'll just define an empty default constructor.

But now I get error C2440: 'initializing' : cannot convert from 'initializer-list' to 'Rect'

So I have to define another constructor for four floats, three floats, two floats and one float to have the same behaviour as before. Also this example is simplified: Rect is actually a union with two Vec2 types as well so I would also have to define a constructor for Rect (Vec2 a, Vec2 b).

Normally all these constructors are generated by the compiler. Is there any way for me to define my Rect(iRect iR) constructor without stopping the compiler from generating all those other constructors?

  • Why are you not using classes for this? – Lawrence Aiello Jul 06 '15 at 15:35
  • 3
    Instead of a converting constructor, you could use a conversion *function* (`operator Rect() const;`). Though I think this is an xy problem – dyp Jul 06 '15 at 15:35
  • [This](http://stackoverflow.com/questions/20828907/the-new-keyword-default-in-c11) might be helpful. – Sam Estep Jul 06 '15 at 15:35
  • 3
    Change `Rect` into `template class Rect {};` and you can reduce a lot of duplicate code. – Captain Obvlious Jul 06 '15 at 15:36
  • @LawrenceAiello `struct` is a `class` just with different default member access. – Captain Obvlious Jul 06 '15 at 15:37
  • @LawrenceAiello struct and class are essentially synonyms in C++ are they not? – Jason Light Jul 06 '15 at 15:38
  • @dyp That could work, but I like implicit conversion. Not that it's essential, and I'll probably end up doing that but I'm curious to see if there's an answer now! – Jason Light Jul 06 '15 at 15:40
  • @CaptainObvlious I might do that too, but again I'm curious to see if this is possible! What if Rect and iRect weren't exactly the same, so can't be templated. – Jason Light Jul 06 '15 at 15:41
  • _"What if Rect and iRect weren't exactly the same, so can't be templated."_ - In the scope of templating they are identical even if they weren't you can still use specialization. If it makes sense to retain an implementation that can be applied across a wide set of types with partial or complete customization specialization allows that to happen. – Captain Obvlious Jul 06 '15 at 15:46
  • @CaptainObvlious Sounds useful, I will look into some of the specifics of templates later on! I've always been put off by how messy template code can get, but I feel like a lot of that is down to the programmers and not the templates themselves. – Jason Light Jul 06 '15 at 15:55
  • You can force the compiler to generate its default constructors in C++11 (eg `Rect() = default; Rect(const Rect&) = default`). The conversion operator's probably better for this specific case though. – celticminstrel Jul 06 '15 at 16:11

3 Answers3

3

Instead of defining a constructor, define a conversion operator:

struct iRect {
    ...
    operator Rect() const {
        return Rect{X,Y,W,H};
    }
};

The conversion operator kicks in whenever an iRect has to be converted to a Rect, either implicitly or explicitly (the explicit keyword can be added to restrict it to explicit conversions only). With this operator defined, you can now write

iRect a;
Rect b = a;

as you'd expect.

Since conversion operators are not constructors, they won't suppress the default constructors - all your aggregate initialization will continue to work.

nneonneo
  • 171,345
  • 36
  • 312
  • 383
  • This seems to be what I'm looking for, although I will add casts to stop the compiler warnings about lost data! I will accept this answer as soon as it lets me. – Jason Light Jul 06 '15 at 15:43
0

Use a conversion function. This type of design is implemented in many libraries (Qt comes to mind). Also, your converting constructor takes an iRect copy for no reason and you should use C++ style casts when possible. You could simply write:

Rect toRect(const iRect& i)
{
    Rect r;
    r.X = static_cast<float>(i.X);
    r.Y = static_cast<float>(i.Y);
    r.W = static_cast<float>(i.W);
    r.H = static_cast<float>(i.H);
    return r;
}

Edit: Damn, @dyp beat me to it.

Bizkit
  • 384
  • 1
  • 10
0

The problem is that without constructors, a default aggregate initializer exists, see here. When defining a custom constructor (any I might say), aggregate intialization isn't possible any longer.

As others have pointed out though, it is better to define a conversion operator

struct iRect {
    //...
    /* explicit */ operator Rect() const {
        return Rect{X,Y,W,H};
    }
};

explicit can be added so that the casting has to be done explicitly for example:

iRect r {1, 2, 3, 4};
Rect r2 = (Rect) r; // Explicit cast
WorldSEnder
  • 4,875
  • 2
  • 28
  • 64