Question

I have the following test code:

#include <cstdint>
#include <cassert>

enum class Result : std::uint32_t {SUCCESS = 0, INSUCCESS = 1};

void* func(Result& result)
{
    // works great
    /*
    result = Result::INSUCCESS;
    return NULL;
    */

    // error: invalid conversion from ‘long int’ to ‘void*’ [-fpermissive]
    /*
    return result = Result::INSUCCESS, NULL;
    */

    // compiles, but <result> is not set???
    return result = Result::INSUCCESS, nullptr;
}

void testReturnWithSideEffects()
{
    Result result = Result::SUCCESS;
    func(result);
    assert(result == Result::INSUCCESS);
}

There are 2 questions here but I'm mostly interested in the second:

Why is result not set?

Edit: Thanks to everyone for confirming this. The work-around that I have decided to use is to replace:

return result = Result::INSUCCESS, nullptr;

with the following:

return result = Result::INSUCCESS, (void*)NULL;

Additional note: of course my production scenario is with another pointer type (not void*) but I simplified for illustrative purposes.

Another note: from the workaround you can tell that there's something fishy going on with that nullptr. I'm guessing that the example line which doesn't compile, should actually compile, and these 2 matters are probably related somehow.

And a 3rd and final note, to those who outlined the "trickery" or "unreadability" of my code: readability is largely a subjective matter, for instance I can argue that a shorthand like this can make the code more structured which can actually help spot defects.

Was it helpful?

Solution

The second case:

return result = Result::INSUCCESS, nullptr;

looks like a gcc bug, it appears to be ignoring the left hand side of the comma operator in the context of a return statement, if I change the return statement to this (see it live):

return (printf("hello\n"), nullptr);

there is no output but if we do this outside of a return statement we get the expected result. It appears to be fixed in 4.8 but I can reproduce with 4.6 and 4.7.

If we look at the draft C++ standard section 5.18 Comma operator it says (emphasis mine):

A pair of expressions separated by a comma is evaluated left-to-right; the left expression is a discarded value expression (Clause 5).83 Every value computation and side effect associated with the left expression is sequenced before every value computation and side effect associated with the right expression. The type and value of the result are the type and value of the right operand; the result is of the same value category as its right operand, and is a bit-field if its right operand is a glvalue and a bit-field. If the value of the right operand is a temporary (12.2), the result is that temporary.

Regardless, as several people have already mentioned this code is clever but hard to read and therefore will be hard to maintain, splitting this into two lines will work fine. I always like to keep in mind this quote from Brian Kernighan (there may be a few other versions of this):

Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?

For the first case, the error is valid, if we look at section 4.10 Pointer conversions which says (emphasis mine going forward):

A null pointer constant is an integral constant expression (5.19) prvalue of integer type that evaluates to zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of object pointer or function pointer type. Such a conversion is called a null pointer conversion.

the expression:

result = Result::INSUCCESS, NULL

is not a constant expression since it contains =, what is a constant expression is covered in section 5.19 Constant expressions and says:

A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression (3.2) [...]

and includes:

an assignment or a compound assignment (5.17); or

Using nullptr is okay since it is a prvalue of std::nullptr_t, we can see that from section 12.14.7 Pointer literals which says:

The pointer literal is the keyword nullptr. It is a prvalue of type std::nullptr_t. [ Note: std::nullptr_t is a distinct type that is neither a pointer type nor a pointer to member type; rather, a prvalue of this type is a null pointer constant and can be converted to a null pointer value or null member pointer value. See 4.10 and4.11. —endnote]

OTHER TIPS

My test indicates that your code should work. I ran this on compileonline.com:

#include <iostream>

using namespace std;

int func5() {
    return 5;
}

int func(int& result) {
    return result = func5(), 10;
}

enum class Result : uint32_t {SUCCESS = 0, INSUCCESS = 1};

void* func(Result& result)
{
    // works great
    /*
    result = Result::INSUCCESS;
    return NULL;
    */

    // error: invalid conversion from ‘long int’ to ‘void*’ [-fpermissive]
    /*
    return result = Result::INSUCCESS, NULL;
    */

    // compiles, but <result> is not set???
    return result = Result::INSUCCESS, nullptr;
}

int main()
{
    int b = 0;
    int a = func(b);
    cout << a << ", " << b << endl;

    Result result = Result::SUCCESS;
    func(result);
    cout << uint32_t(result) << endl;

}

Result:

Compiling the source code....
$g++ -std=c++11 main.cpp -o demo -lm -pthread -lgmpxx -lgmp -lreadline 2>&1

Executing the program....
$demo 
10, 5
1

Perhaps you have a bug in your compiler?

Quote from the C++ standard - comma operator:

The type and value of the result are the type and value of the right operand

so You get 'nullptr', which is converted to the result, also SUCCESS.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top