views:

299

answers:

3
+2  Q: 

C++ brain teaser

I recently refactored code like this (MyClass to MyClassR).

#include <iostream>

class SomeMember
{
public:
  double m_value;

  SomeMember() : m_value(0) {}
  SomeMember(int a) : m_value(a) {}
  SomeMember(int a, int b)
  : m_value(static_cast<double>(a) / 3.14159 +
            static_cast<double>(b) / 2.71828)
  {}
};


class MyClass
{
public:
SomeMember m_first, m_second, m_third;

MyClass(const bool isUp, const int x, const int y)
{
  if (isUp)
  {
    m_first = SomeMember(x);
    m_second = SomeMember(y);
    m_third = SomeMember(x, y);
  }
  else
  {
    m_first = SomeMember(y);
    m_second = SomeMember(x);
    m_third = SomeMember(y, x);
  }
}
};


class MyClassR
{
public:
SomeMember m_first, m_second, m_third;

MyClassR(const bool isUp, const int x, const int y)
: m_first(isUp ? x : y)
, m_second(isUp ? y : x)
, m_third(isUp ? x, y : y, x)
{
}
};


int main()
{
    MyClass a(true, 1, 2);
    MyClassR b(true, 1, 2);

    using namespace std;
    cout.precision(10);
    cout
        << "a:" << endl
        << "\tfirst: " << a.m_first.m_value 
        << "\tsecond: " << a.m_second.m_value 
        << "\tthird: " << a.m_third.m_value << endl;

    cout
        << "b:" << endl
        << "\tfirst: " << b.m_first.m_value
        << "\tsecond: " << b.m_second.m_value
        << "\tthird: " << b.m_third.m_value << endl;

    return 0;
}
  • What is the error,
  • why does it compile (tested with VC6 as well as VC9 warning level 4: no complaints) and
  • what is the right way of doing it?

I (assume) I already have all these answers but I think it's and interesting problem to share.

Update
Extended code so it's "copy & paste & execute"-able. VC9 gave me no complaints either so VC6 is not the problem here.
For completeness, the output is:

a:
        first: 1        second: 2       third: 1.054069532
b:
        first: 1        second: 2       third: 1.004499999
+8  A: 

I’m not sure what exactly you expect but let’s start …

  • First off, ditch VC6. Seriously. Using it is a huge problem since it’s just not standards conforming and precludes a lot of options. Using it correctly is like playing Russian roulette.

  • Your constructor of m_third doesn’t do what you think it does. You cannot write a conditional expression like this: “several parameters” is not a valid expression in C++, and the conditional operator works on expressions.

  • The code compiles because it’s still correct, it just doesn’t do what you want it to. Instead of using “several parameters”, it evaluates the sequence point operator (,) which just takes the last value of the expression, so your conditional is effectively equivalent to: isUp ? y : x

  • The right way is to use two conditionals: m_third(isUp ? x : y, isUp ? y : x)

  • The third constructor of SomeMember is wrong, the value may overflow, yielding a negative value – I highly doubt that that’s what you want.

Konrad Rudolph
(1) VC6 is not my choice, unfortunately.. (2) The SomeMember class is just there to provide some code that compiles. I thought of the overflow problem and made m_value a long instead of and int so I assumed, any 2 ints would fit in there, don't they?
mxp
Please storm into the room where the person forcing you to use VC6 is residing, and throw a chair at them. For me. You may optionally throw additional chairs for other SO members. I recommend ensuring the legs of the chair are aimed at the head for maximum pain.
GMan
Could somebody throw some parenthesis or something around stuff to make it clear what is happening where? I *thought* I understood what the code did, and reading this answer I *still* think I understand what the code does, and yet I am apparently wrong.
Dennis Zickefoose
@mxp:Well, no. On a Windows platform `long` and `int` are effectively the same - signed 32-bit integers.
slacker
@Dennis Zickefoose:???Where could you even TRY to put more parenthesis in this code? I can't see a place where this is possible (aside from paren'ing a single value or *another* parenthesis pair, or other such absurdities)
slacker
@Dennis Zickefoose `m_third(isUp ? x, y : y, x)` ~ `m_third( (isUp ? (x,y) : y), x)`
Charles Bailey
Not in such a way that it remains valid. Just in such a way that it is clear what is going on. How does the compiler read this? I am apparently missing something somewhere.
Dennis Zickefoose
@Mr Bailey: Ahhh.... gotcha, thank.
Dennis Zickefoose
@slacker: You are correct. I updated it so now it's `double`. This time I even checked that it works.
mxp
@mxp: That still isn't technically guaranteed to work. For example, double could be 64-bit and so could int, and double wouldn't be able to to represent every value.
GMan
@GMan: Now dividing the summands by a value greater or equal to 2. No more overflows on any platforms, I hope. :-)
mxp
+2  A: 
m_third(isUp ? x, y : y, x)

This looks wrong to be. The first x is a pointless expression as it has no side effects and the result is not used, then the two sides of the : have the same value and side effects so ?: can be elimintated as the expression before the ? also has no side effects.

m_third(y, x)

But now it doesn't do what the original code does... is this the error?

Charles Bailey
In a comment to a deleted answer you state thatÑ 'A comma-expression isn't a valid final subexpression of a consitional expression'. Why would it not be so?
David Rodríguez - dribeas
@dribeas: Them's the grammar rules! The last bit of a conditional expression has to be an assignment expression, and that's more restrictive than just _expression_. This means that the comma after the second way ends the conditional expression and must be interpreted as part of something else. In this case the _expression-list_ that's the member initializer. You could put parentheses around the comma expression to turn it into a primary expression and this would be a valid final part of a conditional expression.
Charles Bailey
Yes, this is the error. The original code (`MyClass`) called `SomeMember(x, y)` or `SomeMember(y, x)` depending on the `isUp` flag. The refactored code (`MyClassR`) was intended to do the same but it doesn't.
mxp
A: 

What is the error what is the right way of doing it?

I guess your intention is to show some kind of naive usage of comma operator in combination with ternary ?, perhaps there is some clever and unexpected operator priority gotcha hidden, but that I think the code is absolutely artificial. If this is the point, than I would say the "right way of doing it" is do not use C++ or first learn it before using it. Yes, it has many constructs which may look like "quirks" and you can create a lot of strangely looking code accepted by a compiler. By using C++ I would say you are assumed to know the tools.

Why is does compile

Because it contains no error and it is a correct C++ code with no ambiguities.

Suma