键入 trait 以获取默认参数升级

Type trait to obtain default argument promotions

本文关键字:参数 默认 获取 trait 键入      更新时间:2023-10-16

[免责声明:我知道这个问题的答案。我想这可能是一些普遍的兴趣。

问题:我们如何获得一个类型特征来生成执行默认参数提升所生成的类型?

动机:我希望能够方便地使用变量参数。例如:

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

传递给没有参数的函数调用时,即匹配省略号,参数将进行默认参数提升。到目前为止一切顺利,但这些促销活动取决于平台。我可以用va_arg(ap, T)恢复参数,但是T是什么?

现在,对于一些简单的情况,这很容易:例如,我总是可以说:

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

默认升级将导致signed intunsigned int,但根据 C11 7.16.1.1/3,va-cast 到 unsigned int 总是可以的,因为即使默认升级导致int,原始值也可以由两种类型表示。

但是当我期望char32_t时,我应该投到什么类型?C++11 4.5/2 使生成的类型保持完全打开状态。所以我想要一个让我写的特质:

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

怎么做?

当参数类型不得作为变量参数传递时,生成静态断言的特征的奖励积分。

下面是适用于"大多数"类型(积分、浮点数、无作用域枚举、数组、指针、指向成员的指针、函数、函数指针(的解决方案的框架。

#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>()));
};

它不为无法安全通过省略号的类型提供诊断。此外,此解决方案包含类型在作为变量函数参数传递时所经历的衰减,因此它不仅仅是关于提升的。

它的工作原理是显式处理指针到成员类型和浮点转换,并依赖于整型和无作用域枚举类型的一元运算符+;例如 C++11 5.3.1/7:

一元运算符的操作数+应具有算术、无作用域枚举或指针类型,结果是参数的值。整型提升对整型或枚举操作数执行。结果的类型是升级的操作数的类型。

处理枚举需要一些额外的工作,因为可能会重载枚举(作用域和无作用域(的运算符,因此必须谨慎使用朴素的一元加运算符。也就是说,当枚举无作用域时,我们必须考虑底层类型的提升,并完全禁止作用域内的枚举。

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

如果有一行解决方案,我会考虑自杀:)。
我可能会用fits<T, U>模板来改进它,我也会修复
不雅的wchar_tchar16_t char32_t代码重复。

我认为避免operator+可能会稍微好一点。 ?:不能重载,但实际重要的检查也可以用它执行,并且通过将另一个操作数设置为文字 0,所有形式的指针类型都可以自动正确处理:

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

更新:通过 ... 传递任何类型在未计算的表达式中有效,但在可能计算的表达式中,它是有条件支持的。constexpr函数中的完整表达式可能会被计算,并可用于在不支持通过 ... 传递该类型的实现上强制出错。