Reference:
[kernnel][C][C++] ternary conditional operator trick in action
Lightning Talk: ?: - Jonathan Müller - C++Now 2026
#include <iostream>
#include <type_traits>
int main() {
bool cond = true;
// 1. cond ? -1 : 1
// Both operands are int, so the resulting type is int.
auto r1 = cond ? -1 : 1;
static_assert(std::is_same_v<decltype(r1), int>);
// 2. cond ? -1 : 87654321
// Both operands fit in int (decimal literal without suffix is int), resulting in int.
auto r2 = cond ? -1 : 87654321;
static_assert(std::is_same_v<decltype(r2), int>);
// 3. cond ? -1 : 0x87654321
// On systems with 32-bit int, 0x87654321 exceeds INT_MAX (2,147,483,647).
// Hexadecimal integer literals without suffixes take the first type in:
// int -> unsigned int -> long int -> unsigned long int ... that can represent the value.
// Thus, 0x87654321 is typed as `unsigned int`.
// By the usual arithmetic conversions for the ternary operator,
// int (-1) is converted to unsigned int, yielding an unsigned result.
auto r3 = cond ? -1 : 0x87654321;
static_assert(std::is_same_v<decltype(r3), unsigned int>);
std::cout << "r1: " << r1 << " (type: int)\n";
std::cout << "r2: " << r2 << " (type: int)\n";
std::cout << "r3: " << r3 << " (type: unsigned int, value: " << r3 << ")\n";
return 0;
}
#include <iostream>
#include <cstdint>
#include <type_traits>
int main() {
bool cond = true;
// 1. cond ? 'a' : 1
// 'a' has type char, and 1 has type int.
// By the usual arithmetic conversions, char is promoted to int.
auto r1 = cond ? 'a' : 1;
static_assert(std::is_same_v<decltype(r1), int>);
// 2. cond ? 'a' : short(11)
// Types smaller than int (here char and short) undergo integral promotion
// to int before determining the common type, resulting in int.
auto r2 = cond ? 'a' : short(11);
static_assert(std::is_same_v<decltype(r2), int>);
// 3. cond ? std::uint8_t(1) : std::int8_t(-1)
// Both uint8_t and int8_t promote to int because int can represent
// all values of both types (0..255 and -128..127).
auto r3 = cond ? std::uint8_t(1) : std::int8_t(-1);
static_assert(std::is_same_v<decltype(r3), int>);
std::cout << "r1: " << r1 << " (type: int)\n";
std::cout << "r2: " << r2 << " (type: int)\n";
std::cout << "r3: " << r3 << " (type: int)\n";
return 0;
}
#include <iostream>
#include <type_traits>
enum A { a1, a2 };
enum B { b1, b2 };
int main() {
bool cond = true;
// 1. cond ? a1 : a2
// Both operands are of the exact same unscoped enum type `A`,
// so the resulting common type remains `A`.
auto r1 = cond ? a1 : a2;
static_assert(std::is_same_v<decltype(r1), A>);
// 2. cond ? a1 : 11
// One operand is enum `A` and the other is `int`.
// Unscoped enums undergo integral promotion to their underlying type / `int`.
// The common type resolves to `int`.
auto r2 = cond ? a1 : 11;
static_assert(std::is_same_v<decltype(r2), int>);
// 3. cond ? a1 : b1
// The operands are of distinct unscoped enum types `A` and `B`.
// Both undergo integral promotions to `int` via the usual arithmetic conversions,
// resulting in `int`.
auto r3 = cond ? a1 : b1;
static_assert(std::is_same_v<decltype(r3), int>);
std::cout << "r1: " << r1 << " (type: A)\n";
std::cout << "r2: " << r2 << " (type: int)\n";
std::cout << "r3: " << r3 << " (type: int)\n";
return 0;
}
#include <iostream>
#include <type_traits>
enum A { a1, a2 };
// "We have `std::constant_wrapper` at home"[cite: 4]
template <auto V>
using constant = std::integral_constant<decltype(V), V>;
int main() {
bool cond = true;
// 1. cond ? constant<0> : constant<0>
// Both operands are the exact same type: constant<0>.
auto r1 = cond ? constant<0>{} : constant<0>{};
static_assert(std::is_same_v<decltype(r1), constant<0>>);
// 2. cond ? constant<0> : constant<1>
// Distinct types: std::integral_constant provides `constexpr operator value_type()`.
// Both convert implicitly to int, so overload resolution selects int.
auto r2 = cond ? constant<0>{} : constant<1>{};
static_assert(std::is_same_v<decltype(r2), int>);
// 3. cond ? constant<a1> : constant<a1>
// Both operands are the exact same type: constant<a1>.
auto r3 = cond ? constant<a1>{} : constant<a1>{};
static_assert(std::is_same_v<decltype(r3), constant<a1>>);
// 4. cond ? constant<a1> : constant<a2>
// Both operands provide conversion operators to `A` (`operator A()`).
// However, built-in conditional operator candidates [over.built] only exist
// for pointers, pointer-to-members, scoped enumerations, and promoted arithmetic types.
// Because `A` is an unscoped enumeration, it undergoes integral promotion,
// making `int operator?:(bool, int, int)` the candidate chosen, resulting in `int`.
auto r4 = cond ? constant<a1>{} : constant<a2>{};
static_assert(std::is_same_v<decltype(r4), int>);
std::cout << "r1 type: constant<0>\n";
std::cout << "r2 type: int\n";
std::cout << "r3 type: constant<a1>\n";
std::cout << "r4 type: int\n";
return 0;
}
#include <iostream>
#include <cstdint>
#include <type_traits>
template <auto V>
using constant = std::integral_constant<decltype(V), V>;
int main() {
bool cond = true;
// 1. cond ? constant<'a'> : constant<short(11)>
// Both operands are distinct types. Their implicit conversion operators
// yield `char` and `short`, which both undergo integral promotion to `int`.
// The built-in candidate `int operator?:(bool, int, int)` is selected.
auto r1 = cond ? constant<'a'>{} : constant<short(11)>{};
static_assert(std::is_same_v<decltype(r1), int>);
// 2. cond ? constant<std::uint8_t(1)> : constant<std::int8_t(-1)>
// Operands implicitly convert to `std::uint8_t` and `std::int8_t`.
// Both types promote to `int` via the usual arithmetic conversions,
// resulting in `int`.
auto r2 = cond ? constant<std::uint8_t(1)>{} : constant<std::int8_t(-1)>{};
static_assert(std::is_same_v<decltype(r2), int>);
// 3. cond ? constant<std::uint8_t(1)> : constant<std::uint8_t(2)>
// Even though both convert to the same underlying type (std::uint8_t),
// they are distinct class template instantiations. Overload resolution for
// the conditional operator considers built-in candidate operators [over.built].
// Built-in candidates exist only for promoted arithmetic types (at least `int`),
// not for small types like `std::uint8_t`, so the common result type promotes to `int`.
auto r3 = cond ? constant<std::uint8_t(1)>{} : constant<std::uint8_t(2)>{};
static_assert(std::is_same_v<decltype(r3), int>);
std::cout << "r1: " << r1 << " (type: int)\n";
std::cout << "r2: " << r2 << " (type: int)\n";
std::cout << "r3: " << r3 << " (type: int)\n";
return 0;
}
#include <iostream>
#include <type_traits>
#include <utility>
int main() {
bool cond = true;
int i = 0;
const int ci = 0;
// 1. cond ? i : ci -> const int&
// Both operands are lvalues differing only in cv-qualification.
// The result is an lvalue of the cv-combined type (const int), yielding `const int&` under decltype.
decltype(auto) r1 = cond ? i : ci; //[cite: 6]
static_assert(std::is_same_v<decltype(cond ? i : ci), const int&>); //[cite: 6]
// 2. cond ? std::move(i) : std::move(ci) -> const int&&
// Both operands are xvalues (rvalue references) differing only in cv-qualification.
// The result is an xvalue of the cv-combined type (const int), yielding `const int&&` under decltype.
decltype(auto) r2 = cond ? std::move(i) : std::move(ci); //[cite: 6]
static_assert(std::is_same_v<decltype(cond ? std::move(i) : std::move(ci)), const int&&>); //[cite: 6]
// 3. cond ? i : std::move(ci) -> int
// One operand is an lvalue (`int&`) and the other is an xvalue (`const int&&`).
// Because value categories differ and cannot bind to a common reference category directly,
// lvalue-to-rvalue conversion is applied to both, resulting in a prvalue of type `int`.
decltype(auto) r3 = cond ? i : std::move(ci); //[cite: 6]
static_assert(std::is_same_v<decltype(cond ? i : std::move(ci)), int>); //[cite: 6]
std::cout << "r1: " << r1 << " (type: const int&)\n";
std::cout << "r2: " << r2 << " (type: const int&&)\n";
std::cout << "r3: " << r3 << " (type: int)\n";
return 0;
}
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.