Functions

  • int64_t operator<< (std::ostream & , short value)

    MakeCheckOpString is being specialized for every T and U pair that is being

    passed to the CHECK_op macros. However, there is a lot of redundancy in these

    specializations that creates unnecessary library and binary bloat.

    The number of instantiations tends to be O(n^2) because we have two

    independent inputs. This technique works by reducing `n`.

    Most user-defined types being passed to CHECK_op end up being printed as a

    builtin type. For example, enums tend to be implicitly converted to its

    underlying type when calling operator

    <

    <

    , and pointers are printed with the

    `const void*` overload.

    To reduce the number of instantiations we coerce these values before calling

    MakeCheckOpString instead of inside it.

    To detect if this coercion is needed, we duplicate all the relevant

    operator

    <

    <

    overloads as specified in the standard, just in a different

    namespace. If the call to `stream

    <

    <

    value` becomes ambiguous, it means that

    one of these overloads is the one selected by overload resolution. We then

    do overload resolution again just with our overload set to see which one gets

    selected. That tells us which type to coerce to.

    If the augmented call was not ambiguous, it means that none of these were

    selected and we can't coerce the input.

    As a secondary step to reduce code duplication, we promote integral types to

    their 64-bit variant. This does not change the printed value, but reduces the

    number of instantiations even further. Promoting an integer is very cheap at

    the call site.

  • int64_t operator<< (std::ostream & , unsigned short value)
  • int64_t operator<< (std::ostream & , int value)
  • int64_t operator<< (std::ostream & , unsigned int value)
  • int64_t operator<< (std::ostream & , long value)
  • uint64_t operator<< (std::ostream & , unsigned long value)
  • int64_t operator<< (std::ostream & , long long value)
  • uint64_t operator<< (std::ostream & , unsigned long long value)
  • float operator<< (std::ostream & , float value)
  • double operator<< (std::ostream & , double value)
  • long double operator<< (std::ostream & , long double value)
  • bool operator<< (std::ostream & , bool value)
  • const void * operator<< (std::ostream & , const void * value)
  • const void * operator<< (std::ostream & , std::nullptr_t )
  • template <typename Traits>
    char operator<< (std::basic_ostream<char, Traits> & , char )

    These `char` overloads are specified like this in the standard, so we have to

    write them exactly the same to ensure the call is ambiguous.

    If we wrote it in a different way (eg taking std::ostream instead of the

    template) then one call might have a higher rank than the other and it would

    not be ambiguous.

  • template <typename Traits>
    signed char operator<< (std::basic_ostream<char, Traits> & , signed char )
  • template <typename Traits>
    unsigned char operator<< (std::basic_ostream<char, Traits> & , unsigned char )
  • template <typename Traits>
    const char * operator<< (std::basic_ostream<char, Traits> & , const char * )
  • template <typename Traits>
    const signed char * operator<< (std::basic_ostream<char, Traits> & , const signed char * )
  • template <typename Traits>
    const unsigned char * operator<< (std::basic_ostream<char, Traits> & , const unsigned char * )
  • template <typename T, typename = decltype(std::declval<std::ostream&>()
                                                                          << std::declval<const T&>())>
    const T & Detect (int )

    This overload triggers when the call is not ambiguous.

    It means that T is being printed with some overload not on this list.

    We keep the value as `const T

    &

    `.

  • template <typename T>
    decltype(detect_specialization::operator<<(std::declval<std::ostream &>(), std::declval<const T &>())) Detect (char )

    This overload triggers when the call is ambiguous.

    It means that T is either one from this list or printed as one from this

    list. Eg an enum that decays to `int` for printing.

    We ask the overload set to give us the type we want to convert it to.