Type trait to obtain default argument promotions

|▌冷眼眸甩不掉的悲伤 提交于 2019-11-30 14:04:58

问题


[Disclaimer: I know an answer to this question. I thought it might be of some general interest.]

Question: How can we have a type trait that produces the type that results from performing default argument promotions?

Motivation: I would like to be able to use variable arguments portably. For example:

void foo(char const * fmt, ...);  // Please pass: * unsigned short
                                  //              * bool
                                  //              * char32_t
                                  //              * unsigned char

When passing arguments to a function call without parameters, i.e. matching the ellipsis, the arguments undergo default argument promotion. So far so good, but those promotions are platform dependent. I can recover the arguments with va_arg(ap, T), but what is T?

Now, for some simple situations this is easy: For example, I can always say:

unsigned short n = va_args(ap, unsigned int);

The default promotion will result in either a signed int or an unsigned int, but according to, say, C11 7.16.1.1/3, va-casting to unsigned int is always fine, since even if the default promotion results in an int, the original value can be represented by both types.

But what type should I cast to when I expect a char32_t? C++11 4.5/2 leaves the resulting type wide open. So I would like a trait that lets me write:

char32_t c = va_args(ap, default_promote<char32_t>::type);

How to do this?

Bonus points for a trait that produces a static assertion when the parameter type must not be passed as a variable argument.


回答1:


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.




回答2:


#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.




回答3:


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 ....



来源:https://stackoverflow.com/questions/20442347/type-trait-to-obtain-default-argument-promotions

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