[免责声明:我知道这个问题的答案。我认为这可能有一些普遍的兴趣。]
问题:我们怎样才能有一个类型特征产生由执行默认参数促销产生的类型?
动机:我希望能够移植使用变量参数。例如:
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 int
或unsigned 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);
怎么做?
当参数类型不能作为变量参数传递时,产生静态断言的特征的加值点。
答案 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_t
和char16_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
函数中的完整表达式可能被评估,可用于强制执行不支持通过...
传递该类型的实现。