4

What do you specify the return type to be for the following function which should act like ?: but without the laziness?

My first attempt was the following:

template <typename T1, typename T2>
T1 myif(bool b, T1&& true_result, T2&& false_result)
{
    if (b) {
        return true_result;
    } else {
        return false_result;
    }
}

But then I found given:

int f() { return 42; }
int x = 5;

whilst

(true ? x : f())++; 

falls to compile,

myif(true, x, f())++;

compiles fine and returns a dangling reference.

My second attempt was to change the return type to:

typename std::remove_reference<T1>::type

but then

(true ? x : x)++

works, but:

myif(true, x, x)++

does not as I'm returning by value now.

Even:

auto myif(bool b, T1&& true_result, T2&& false_result) 
  -> typeof(b ? true_result : false_result)

fails, I'm not sure why, perhaps typeof converts it's argument to a value type. In any case the point was to express the type explicitly, not through auto and typeof.

Any idea how to make a function that returns the same type as ?:?

Stefan Hanke
  • 3,458
  • 2
  • 30
  • 34
Clinton
  • 22,361
  • 15
  • 67
  • 163
  • 6
    `std::common_type`, but that doesn't preserve value categories since the result is `std::decay`'d. Also, the C++11 spelling is `decltype`, not `typeof`. – T.C. Feb 05 '15 at 16:48
  • 3
    Why not `decltype(b ? std::forward(true_result) : std::forward(false_result))`? – Casey Feb 05 '15 at 16:53
  • Anyway, the rules for `?:` occupies 1.5 pages of the standard. Some parts of it requires knowing things that is AFAIK impossible to determine programmatically (e.g., whether an expression can be converted to `T&` or `T&&` *subject to the constraint that the reference must bind directly*); others are possible but extremely tedious to implement without `decltype` (e.g., the usual arithmetic conversions). – T.C. Feb 05 '15 at 17:07
  • If you need C++03 compatibility, you may wanna lookup this one: http://stackoverflow.com/a/2450157/34509 . – Johannes Schaub - litb Feb 08 '15 at 18:15

1 Answers1

1

I believe the best way is what Casey proposed:

template <typename T1, typename T2> 
auto myif(bool b, T1&& true_result, T2&& false_result)
    -> decltype(b ? std::forward<T1>(true_result) : std::forward<T2>(false_result))
{
    if (b) {
        return true_result;
    } else {
        return false_result;
    }   
}

Which, in C++14, becomes just:

template <typename T1, typename T2> 
decltype(auto) myif(bool b, T1&& true_result, T2&& false_result)
{
    // same body
}

Given that:

int f() { return 42; }
int x = 5, y = 7;

myif(true, x, f())++; // error: lvalue required as increment operand
myif(false, x, y)++;  // OK
Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Does that ever actually compile if the types are different? the bool isn't a compile time constant, so how can the compiler know what type the result should be, unless they're the same type to begin with...? – user673679 Feb 05 '15 at 21:24
  • @user673679 Yeah of course. As long as there's a common type (you can't do like `b ? "hello" : 4.2` for instance). See [reference](http://en.cppreference.com/w/cpp/language/operator_other) for a longer explanation. – Barry Feb 05 '15 at 21:35
  • Thanks. I guess I hadn't really thought about the ternary operator like that before. – user673679 Feb 05 '15 at 21:43
  • Your C++14 version is broken. "If a function with a declared return type that contains a placeholder type has multiple return statements, the return type is deduced for each return statement. If the type deduced is not the same in each deduction, the program is ill-formed." – Ben Voigt Feb 15 '15 at 18:15
  • Both versions are broken if the types are the same. If `T1` and `T2` are both `int`, then in the C++11 version, the return type gets declared as `int &&`. In the C++14 version, `decltype(auto)` causes the return type to be deduced as `int &&`. Either way, both `true_result` and `false_result` are lvalues which cannot bind to an rvalue reference. –  Feb 15 '15 at 18:16