Type trait to obtain default argument promotions

隐身守侯 提交于 2019-11-30 09:37:35

Here's a skeleton of a solution that works for "most" types (integral, float, unscoped enumeration, arrays, pointers, pointers-to-member, functions, function pointers).

#include <type_traits>

template <typename U>
struct default_promote
{
    // Support trait for scoped enums

    template <typename E, bool IsEnum>
    struct is_unscoped_enum : std::false_type { };

    template <typename E> struct is_unscoped_enum<E, true>
    : std::is_convertible<E, typename std::underlying_type<E>::type> { };


    // Floating point promotion

    static double test(float);


    // Integral promotions (includes pointers, arrays and functions)

    template <typename T, typename = typename std::enable_if<!is_unscoped_enum<T, std::is_enum<T>::value>::value>::type>
    static auto test(T) -> decltype(+ std::declval<T>());

    template <typename T, typename = typename std::enable_if<is_unscoped_enum<T, std::is_enum<T>::value>::value>::type>
    static auto test(T) -> decltype(+ std::declval<typename std::underlying_type<T>::type>());


    // Pointers-to-member (no promotion)

    template <typename T, typename S>
    static auto test(S T::*) -> S T::*;


    using type = decltype(test(std::declval<U>()));
};

It does not provide diagnostics for types that cannot safely be passed through an ellipsis. Also, this solution subsumes the decay that types undergo when passed as variable function arguments, so it is not strictly about promotion only.

It works by handling explicitly the pointer-to-member types and the floating point conversion, and by relying on the unary operator + for integral and unscoped enumeration types; e.g. C++11 5.3.1/7:

The operand of the unary + operator shall have arithmetic, unscoped enumeration, or pointer type and the result is the value of the argument. Integral promotion is performed on integral or enumeration operands. The type of the result is the type of the promoted operand.

Some extra work is needed to handle enumerations, since it is possible to overload operators for enumerations (both scoped and unscoped), and so the naive unary plus operator must be used with care. That is, we must consider the promotion of the underlying type when the enum is unscoped, and forbid scoped enums entirely.

#include <cstdlib>
#include <stdarg.h>
#include <type_traits>

// Utility type if / else
template <typename T, typename U, bool choose>
struct TifChooseU { };

template <typename T, typename U>
struct TifChooseU <T, U, true> {
    using type = T;
};

template <typename T, typename U>
struct TifChooseU <T, U, false> {
    using type = U;
};

// Default - No Promotion
template <typename T>
struct promote_me {
    using type = T;
};

// http://en.cppreference.com/w/cpp/language/implicit_cast - Let's go in order
// Signed char - int
template <>
struct promote_me <signed char> {
    using type = int;
};

// Signed short - int
template <>
struct promote_me <signed short> {
    using type = int;
};

// Unsigned char - int or unsigned int, dependent on inter-stellar configuration
template <>
struct promote_me <unsigned char> {
    // Doesn't compile without the parens around the operator >
    using type = TifChooseU <int, unsigned int, (sizeof(int) > sizeof(unsigned char))>::type;
};

// Unsigned short - int or unsigned int, dependent on inter-stellar configuration
template <>
struct promote_me <unsigned short> {
    // Doesn't compile without the parens around the operator >
    using type = TifChooseU <int, unsigned int, (sizeof(int) > sizeof(short))>::type;
};

// Char - dispatch to unsigned / signed char
template <>
struct promote_me <char> :
       promote_me <TifChooseU <signed char, unsigned char,
                               std::is_signed<char>::value>::type> {};

// Wchar_t - int, unsigned int, long, unsigned long, long long, unsigned long long
// dependent on the amount of goats recently sacrificed
template <>
struct promote_me <wchar_t> {
    using type =
        TifChooseU <
        TifChooseU <int,
            TifChooseU<long, long long, (sizeof(long) > sizeof(wchar_t))>::type,
            (sizeof(int) > sizeof(wchar_t))>::type,
        TifChooseU <unsigned int,
            TifChooseU<unsigned long, unsigned long long, (sizeof(unsigned long) > sizeof(wchar_t))>::type,
            (sizeof(int) > sizeof(wchar_t))>::type,
        std::is_signed<wchar_t>::value
    >::type;
};

// Char16_t - int, unsigned int, long, unsigned long, long long, unsigned long long
// dependent on the amount of goats recently sacrificed
template <>
struct promote_me <char16_t> {
    using type =
        TifChooseU <
        TifChooseU <int,
            TifChooseU<long, long long, (sizeof(long) > sizeof(char16_t))>::type,
            (sizeof(int) > sizeof(char16_t))>::type,
        TifChooseU <unsigned int,
            TifChooseU<unsigned long, unsigned long long, (sizeof(unsigned long) > sizeof(char16_t))>::type,
            (sizeof(int) > sizeof(char16_t))>::type,
        std::is_signed<char16_t>::value
    >::type;
};

// Char32_t - int, unsigned int, long, unsigned long, long long, unsigned long long
// dependent on the amount of goats recently sacrificed
template <>
struct promote_me <char32_t> {
    using type =
        TifChooseU <
        TifChooseU <int,
            TifChooseU<long, long long, (sizeof(long) > sizeof(char32_t))>::type,
            (sizeof(int) > sizeof(char32_t))>::type,
        TifChooseU <unsigned int,
            TifChooseU<unsigned long, unsigned long long, (sizeof(unsigned long) > sizeof(char32_t))>::type,
            (sizeof(int) > sizeof(char32_t))>::type,
        std::is_signed<char32_t>::value
    >::type;
};

// Enums and Bitfields - maybe later ^^

// Bool - int
template <>
struct promote_me <bool> {
    using type = int;
};

void foo(const char* fmt, ...) {
    va_list va;
    va_start(va, fmt);
    unsigned short a = va_arg(va, promote_me<unsigned short>::type);
    bool           b = va_arg(va, promote_me<bool>::type);
    char32_t       c = va_arg(va, promote_me<char32_t>::type);
    unsigned char  d = va_arg(va, promote_me<unsigned char>::type);
}

int main() {

const char* fmt;
unsigned short a = 1;
bool           b = true;
char32_t       c = 'a';
unsigned char  d = 'c';

foo(fmt, a, b, c, d);

}

If there is like a one-liner solution to this, I'll consider a suicide :).
I will probably improve this with fits<T, U> template and I'll also fix
the inelegant wchar_t and char16_t char32_t code repetition.

I think it may be slightly better to avoid operator+. ?: cannot be overloaded, the checks that actually matter can be performed with that as well, and by making the other operand a literal 0, all forms of pointer types get handled properly automatically:

// nullptr_t promotes to void *
template <typename T, typename = typename std::enable_if<std::is_same<T, std::nullptr_t>::value>::type>
void *default_promote_impl(T);

// float promotes to double
template <typename T, typename = typename std::enable_if<std::is_same<T, float>::value>::type>
double default_promote_impl(T);

// scalar types other than nullptr_t/float that have a conversion from/to 0 promote to their common type
// this also matches function and array types, after their implicit conversion to a pointer type
template <typename T, typename = typename std::enable_if<std::is_scalar<T>::value && !std::is_same<T, std::nullptr_t>::value && !std::is_same<T, float>::value>::type>
decltype(true ? 0 : std::declval<T>()) default_promote_impl(T);

// scoped enumeration types don't get promoted
template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type>
typename std::enable_if<!std::is_convertible<T, typename std::underlying_type<T>::type>::value, T>::type default_promote_impl(T);

// class types don't get promoted
template <typename T, typename = typename std::enable_if<std::is_class<T>::value || std::is_union<T>::value>::type>
T default_promote_impl(T);

template <typename T>
constexpr bool check_vararg_passable(...) {
  return true ? true : check_vararg_passable<T>(*(typename std::remove_reference<T>::type *)0);
}

template <typename T, bool = check_vararg_passable<T>()>
struct default_promote {
  typedef decltype(default_promote_impl(std::declval<T>())) type;
};

Update: passing any type through ... is valid in unevaluated expressions, but in potentially evaluated expressions, it's conditionally-supported. The full expression in a constexpr function is potentially evaluated and can be used to force an error on implementations that don't support passing that type through ....

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!