输入trait以获取默认参数促销

时间:2013-12-07 14:14:56

标签: c++ typetraits variadic-functions

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

问题:我们怎样才能有一个类型特征产生由执行默认参数促销产生的类型?

动机:我希望能够移植使用变量参数。例如:

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-casting unsigned int总是很好,因为即使默认促销结果为int,原始值也可以由两种类型表示。

但是当我期待char32_t时,我应该选择什么类型? C ++ 11 4.5 / 2使得结果类型大开。所以我想要一个让我写的特质:

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

怎么做?

当参数类型不能作为变量参数传递时,产生静态断言的特征的加值点。

3 个答案:

答案 0 :(得分:3)

这是一个解决方案的框架,适用于“大多数”类型(整数,浮点数,无范围枚举,数组,指针,指向成员,函数,函数指针)。

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

  

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

处理枚举需要一些额外的工作,因为有可能使运算符重载(包括作用域和未作用域),因此必须小心使用天真的一元加运算符。也就是说,当enum是无范围的时候,我们必须考虑提升底层类型,并且完全禁止范围内的枚举。

答案 1 :(得分: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);

}

如果有一个单线解决方案,我会考虑自杀:) 我可能会使用fits<T, U>模板改进这一点,我也会修复 不合适的wchar_tchar16_t char32_t代码重复。

答案 2 :(得分:2)

我认为避免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函数中的完整表达式可能被评估,可用于强制执行不支持通过...传递该类型的实现。