2

For various boring reasons, I need a boxed int class that mostly acts as an int, but is a class that inherits from a base so it can work with other parts of a object hierarchy. I included a constructor that takes an int, as well as an int cast so I can easily intermix my boxed int with regular ints in code. However, I'm seeing a very odd behavior that I can't figure out: When I return my boxed int from a function, I'd like it to use my copy constructor that takes a reference to another BoxedInt. However, instead it casts my boxed int to and int, and then uses my int constructor. This causes problems because in my actual codebase, there are other baseclass properties I want to copy in this case, and they are lost by taking this cast/constructor path. Here is the code in question:

class BoxedInt
{
private:
    int m_int;
public:
    BoxedInt():m_int(0)
    {
        trace(L"Constructed with nothing");
    }

    BoxedInt(int val):m_int(val)
    {
        trace(L"Constructed with int");
    }

    BoxedInt(BoxedInt& val)
    {
        trace(L"Constructed with reference");
        m_int = val.m_int;
    }

    operator int()
    {
        trace(L"Cast to int");
        return m_int;
    }
};

BoxedInt funky()
{
    BoxedInt TempInt = 1;
    return TempInt;
}

int main(int argc, char* argv[])
{
    trace(L"Start");
    BoxedInt test1 = 1;
    trace(L"Copying");
    BoxedInt test2 = test1;
    trace(L"Assigning from return value");
    BoxedInt test3 = funky();
    trace(L"Done");
    return 0;
}

When this is run, here's the output:

Start
Constructed with int
Copying
Constructed with reference
Assigning from return value
Constructed with int
Constructed with reference
Cast to int
Constructed with int
Done

So when I assign one value to another, the reference based constructor is used, as I'd expect. However, when I assign the return value of my function to a BoxedInt, for some reason the compiler decides to cast to int, then use the int constructor. My C++ is rusty and I'm having trouble getting to the bottom of this odd compiler decision that I can't seem to counteract. Any ideas?

  • Read about explicit keyword http://stackoverflow.com/questions/121162/what-does-the-explicit-keyword-in-c-mean – fasked Oct 15 '13 at 16:52
  • The compiler doesn't **cast** the return value to `int`, it **converts** it. A cast is something you write in your source code to tell the compiler to do a conversion. Similarly, `operator int` is a **conversion operator**, not a cast. – Pete Becker Oct 15 '13 at 18:51

3 Answers3

9

Your copy constructor takes a reference to a non-const, so it cannot be called with a temporary. The return value of funky() is a temporary, so the copy constructor cannot be used to construct test3.

Make the copy constructor take a const reference, and it should be OK.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • 2
    I feel like a moron, I noticed that as soon as I posted, but it was too late to delete! You guys are too fast. – Jeremy Friesen Oct 15 '13 at 17:02
  • @Raja I'm not sure that's a good page to refer to. Herb explains the issues well, but the example he uses is a case where you shouldn't be using references to begin with, const or otherwise; the fact that he says nothing about this might suggest to many readers that the use of a const reference here is good programming practice. – James Kanze Oct 16 '13 at 09:27
5

Your copy-constructor takes a non-const reference, which means you can't bind a temporary to the parameter, which is exactly what your desired method of returning would do. Therefore, the compiler chooses the other route.

Change your copy-constructor:

BoxedInt(const BoxedInt &val) {

In fact, Clang 3.4 gives an error because of the BoxedInt test1 = 1;.

chris
  • 60,560
  • 13
  • 143
  • 205
  • 1
    I think Clang is right. Copy initialization requires an accessible copy constructor. And the way I would interpret it, this means an accessible copy constructor to copy the temporary that is at least theoretically constructed. – James Kanze Oct 15 '13 at 16:56
  • @JamesKanze, I completely agree. – chris Oct 15 '13 at 16:57
  • 2
    GCC 4.8.1 also complains in `BoxedInt test1 = 1;` as it should. – Cassio Neri Oct 15 '13 at 16:58
  • Yes. I wondered about this when I was writing my answer. I wasn't sure, but I didn't think he should have gotten beyond the initialization with an `int`. Since the context is a bit special, I let it pass, thinking that maybe there was a special rule. I just looked it up, however, and at least in C++11, it is exactly the opposite; the standard says explicitly that what is being copied is a temporary and a prvalue (which means that it can bind to an rvalue reference or a reference to const, but not to a reference to non-const). – James Kanze Oct 15 '13 at 17:01
  • @CassioNeri, I didn't test it myself, but that's relieving to hear, thanks. – chris Oct 15 '13 at 17:01
1

I believe the issue (or one of them) is the copy constructor signature:

BoxedInt(BoxedInt& val)

It should be

BoxedInt(const BoxedInt& val)
Cassio Neri
  • 19,583
  • 7
  • 46
  • 68