Every self-respecting C / C ++ programmer knows what a ternary operator is and most have used it at least once in their programs. But do you know all the secrets of the ternary operator? What potential dangers are associated with its use and what seemingly unrelated to its intended purpose, the possibilities in it lurk? This article gives you the opportunity to test your knowledge and, perhaps, learn something new.
Let's start with a little test.
Test
Does the following code compile? Explain why.
one.
int i; int j; (false ? i: j) = 45;
2
int i; int j; (true ? i: j) = 45;
3
short i; int j; (true ? i: j) = 45;
four.
return true ? 0 : 1;
five.
true ? return 0 : return 1;
What will be the conclusion of the next piece? Why?
6
std::cout << (false ? 9 : '9') << " " << (true ? 9 : '9');
')
What values will a, b, and c have as a result of executing the following code? Why?
7
int a = 1; int b = 1; int c = 1; a = true ? ++b : ++c;
8. Name the situation where you cannot use
if {...} else {...} , but you can use the ternary operator.
9. What potential dangers are hidden in using the ternary operator? What is their reason?
10. What unexpected uses of the ternary operator come to your mind?
Explanation
So, let's begin. The ternary operator is distinguished from a number of other operators in C ++. It is called "
conditional expression ". Well, since this is an
expression , an expression, then, like every expression, it must have a type and
value category . Actually, by answering the questions what type and
value category of ternary operators in each of the first seven questions of the test, we can easily solve the tasks.
This is where the fun begins. It turns out that the type of a ternary operator is the
most common type of its two last operands. What does the
most common mean? This is easiest to clarify with examples.
Int and
short will have an
int .
A and
B in the following snippet will also have an
int .
struct A{ operator int(){ return 1; } }; struct B{ operator int(){ return 3; } };
Those.
the most common type is the type to which both operands can be cast. There may well be situations where there is no general type. For example,
struct C{}; struct D{};
there is no general type, and the following fragment will not compile at all
(true ? C() : D())
So. We dealt with the type of the ternary operator a bit. It remains to solve the issue with the
value category . The following rule applies here: if a type conversion to the
most general occurs in a ternary operator, the ternary operator is
rvalue . If not, then
lvalue . Now that we know what we know, we can easily answer the first 7 questions.
Answers
1. and 2. - Yes. Type conversion does not occur, and
lvalue may well be assigned a value.
3. - No. Here is the type conversion. This means that the
value category of the expression to the left of the "=" sign is
rvalue . And
rvalue , as you know, can not be assigned.
4. - Yes. We all did it more than once.
5. - No. The point here is that in C ++, the
statement cannot break
expression .
6. The program will display "57 9". In this fragment, due to the fact that the second and third operands are of different types, a conversion to the most general type occurs. In this case,
int . A '9', as is known, has an ASCII code 57.
7. In this question lies another feature of the ternary operator. Namely, only the operand from the second and third, which reaches the thread of execution, is calculated. However, the same behavior can be observed in if {...} else {...}. Accordingly, the values of the variables a, b and c will be 2, 2, 1.
Where it is impossible to use if {...} else {...}, but you can use the ternary operator?
For example, in the constructor initialization list. You cannot write like this:
struct S { S() : if(true) i_(1) else i_(0){} int i_; };
But it can be done like this:
struct S { S() : i_(some_condition ? 0 : 1){} int i_; };
When initializing the link depending on the condition. As you know, you cannot declare a non-initialized link, so the following fragment will not compile:
int a = 3; int b = 4; int& i; if(some_condition) i = a; else i = b;
But the following compiles successfully:
int& i = (some_condition ? a : b)
In C ++ 11, the ternary operator is used much more often. This is due to the fact that in
constexpr functions there should be nothing but
return `expression` . A
`expression` may well be a ternary operator.
As an example, I will give a classic algorithm for determining the simplicity of a number.
constexpr bool check_if_prime_impl(unsigned int num, unsigned int d) { return (d * d > num) ? true : (num % d == 0) ? false : check_if_prime_impl(num, d + 1); } constexpr bool check_if_prime(unsigned int num) { return (num <= 1) ? false : check_if_prime_impl(num, 2); }
In the same example, by the way, one can see the use of cascade ternary operators, which can be of unlimited nesting and replace the multiple if {...} else {...}.
The dangers of a ternary operator
Suppose we have a class
String class String { public: operator const char*(); };
And we can use it, for example, as follows:
const char* s = some_condition ? "abcd" : String("dcba");
As we already know, the second and third operands of the ternary operator are reduced to the most general type. In this case, it is a
const char * . But the
String object
(“dcba”) will be destroyed at the end of the expression and
s will point to
non-valid memory. At best, the program will crash if you try to use
s later. At worst, it will produce incorrect results, causing discontent with the customer and a headache for the programmer.
"Unusual" use of the ternary operator
A ternary operator can be used to define a common type of two or more types. And this, in turn, can be used, for example, to determine whether one type is reduced to another.
template <typename T, typename U> struct common_type { typedef decltype(true ? std::declval<T>() : std::declval<U>()) type; }; template<typename T, typename U> struct is_same{ enum { value = false; } }; template<typename T> struct is_same<T, T>{ enum { value = true; } }; int main() { std::cout << is_same<int, common_type<A, B>::type>::value <<std::endl; }
In fact, if you know the properties of the ternary operator, such use is almost obvious. What is unusual here, perhaps, is that it is not used for its intended purpose, i.e. not to select one of the two values depending on the condition.