Showing posts with label cpp_now_2026. Show all posts
Showing posts with label cpp_now_2026. Show all posts

Nov 2, 2026

[C++] ternary conditional operator

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;
}