Введите trait, чтобы получить продвижение аргумента по умолчанию

[Отказ от ответственности: я знаю ответ на этот вопрос. Я подумал, что это может представлять некоторый общий интерес.]

Вопрос. Как мы можем иметь свойство типа, которое создает тип, полученный в результате выполнения повышения аргумента по умолчанию?

Мотивация: я хотел бы иметь возможность переносимого использования переменных аргументов. Например:

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 к unsigned int всегда допустимо, поскольку даже если повышение по умолчанию приводит к int, исходное значение может быть представлены обоими типами.

Но к какому типу мне следует привести, когда я ожидаю char32_t? C++11 4.5/2 оставляет результирующий тип широко открытым. Поэтому я хотел бы черту, которая позволяет мне писать:

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

Как это сделать?

Бонусные баллы за черту, которая создает статическое утверждение, когда тип параметра не должен передаваться в качестве аргумента переменной.


person Kerrek SB    schedule 07.12.2013    source источник
comment
Я искал информацию о рекламных акциях по умолчанию, но не смог найти ничего, что упоминало бы, что это зависит от платформы (по крайней мере, явно). Вы имеете в виду, что это основано на том факте, что sizeof (int) зависит от платформы?   -  person Tamás Szelei    schedule 07.12.2013
comment
@fish: Например, продвижение unsigned short по умолчанию зависит от платформы.   -  person Kerrek SB    schedule 07.12.2013
comment
@KerrekSB Мое решение слишком сложное? Есть ли более простой?   -  person ScarletAmaranth    schedule 07.12.2013
comment
@KerrekSB, пожалуйста, опубликуйте соответствующие стандартные части, когда вы (или кто-то другой) опубликуете ответ. Спасибо за этот пост, очень интересно.   -  person Tamás Szelei    schedule 07.12.2013
comment
@fish: стандартные акции описаны в C++ 11 4.5, а соединение с переменными аргументами — в 5.2.2/7.   -  person Kerrek SB    schedule 07.12.2013
comment
Пользователь @KerrekSB hvd сказал, что decltype можно использовать с оператором + (унарным), template <typename T> struct promote_me_smart { using type = decltype(+T()); }; это действительно работает?   -  person ScarletAmaranth    schedule 07.12.2013
comment
@KerrekSB Похоже, это так, я понятия не имею, почему, не хочешь объяснить?   -  person ScarletAmaranth    schedule 07.12.2013
comment
@ScarletAmaranth Я пытаюсь заставить это работать, но я уже видел, что это не так просто, это пропустит продвижение float-›double, и оно сломается для перечислений с предоставленным пользователем operator+.   -  person    schedule 07.12.2013
comment
@hvd Керрек такой дразнящий.   -  person ScarletAmaranth    schedule 07.12.2013
comment
@ScarletAmaranth: Да, в основном это так. Вероятно, вам придется ввести пару специализаций (например, для работы с float) и, возможно, использовать std::declval, но большую часть тяжелой работы должен выполнять унарный плюс. (Конечно, он работает так, как задумано для указателей, указателей на функции и массивов.)   -  person Kerrek SB    schedule 07.12.2013
comment
@KerrekSB Как я уже говорил ранее, унарный + не будет работать и не может работать с типами перечисления, которые предоставляют настраиваемый оператор +, к сожалению.   -  person    schedule 07.12.2013
comment
@hvd: std::underlying_type спешит на помощь?   -  person Kerrek SB    schedule 07.12.2013
comment
@KerrekSB О, я об этом не подумал, это должно сработать. Ницца.   -  person    schedule 07.12.2013
comment
@KerrekSB Однако убедитесь, что вы исключили типы перечисления с областью действия, поскольку они не продвигаются, даже если базовый тип делает это.   -  person    schedule 07.12.2013


Ответы (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>()));
};

Он не обеспечивает диагностику типов, которые нельзя безопасно передать через многоточие. Кроме того, это решение включает в себя распад, которому подвергаются типы при передаче в качестве аргументов переменных функций, поэтому речь идет не только о продвижении.

Он работает, явно обрабатывая типы указателя на член и преобразование с плавающей запятой, а также полагаясь на унарный оператор + для целочисленных типов и типов перечисления с незаданной областью; например С++ 11 5.3.1/7:

Операнд унарного оператора + должен иметь арифметический тип, перечисление без области действия или тип указателя, а результатом является значение аргумента. Целочисленное повышение выполняется над целочисленными операндами или операндами перечисления. Тип результата — это тип расширенного операнда.

Для обработки перечислений требуется некоторая дополнительная работа, поскольку операторы для перечислений (как с областью действия, так и без области действия) можно перегрузить, поэтому простой унарный оператор плюс следует использовать с осторожностью. То есть мы должны рассмотреть продвижение базового типа, когда перечисление не имеет области действия, и полностью запретить перечисления с областью действия.

person Kerrek SB    schedule 07.12.2013
comment
:) довольно аккуратно, продолжайте задавать хорошие вопросы, я устал отвечать, почему чей-то массив содержит мусор в конце, когда они его печатают - person ScarletAmaranth; 08.12.2013

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

person ScarletAmaranth    schedule 07.12.2013
comment
Хорошо, не стреляйте, но я думаю, что для С++ 11 есть что-то вроде трехстрочного решения (если вы ставите фигурные скобки на новые строки). - person Kerrek SB; 07.12.2013
comment
@KerrekSB Ха-ха-ха, круто :). Хочешь опубликовать? - person ScarletAmaranth; 07.12.2013
comment
@KerrekSB Я оставлю свой ответ здесь, чтобы показать всем, какой я большой идиот, чтобы ваше превосходство могло просветить темные моря невежества :). - person ScarletAmaranth; 07.12.2013
comment
Проверки sizeof теоретически неверны, вам нужно будет проверить numeric_limits<T>::max(), чтобы использовать этот подход для учета типов, в которых не все биты влияют на значение. - person ; 07.12.2013
comment
@hvd Ммммм, я вообще не проверял это, так что вы можете быть здесь, я в конце концов исправлю ответ, если это так :). - person ScarletAmaranth; 07.12.2013
comment
ScarletAmaranth, я начал собирать похожее решение, теперь меня очень интересует 3-х вкладыш от @KerrekSB :) - person Tamás Szelei; 07.12.2013
comment
@fish Я УХОЧУ узнать - person ScarletAmaranth; 07.12.2013
comment
Я думаю, что унарный + в сочетании с decltype можно использовать для проверки интегральных повышений. - person ; 07.12.2013
comment
@hvd Кажется, вы правы, `template ‹typename T› struct Promotion_me_smart { using type = decltype(+T()); }; ` работает, почему, это загадка - person ScarletAmaranth; 07.12.2013
comment
@ScarletAmaranth: любое математическое выражение вызывает повышение, а +var - это выражение... - person Mooing Duck; 07.12.2013
comment
@MooingDuck, это объяснило бы это :) - person ScarletAmaranth; 07.12.2013
comment
@MooingDuck: см. 5.3.1/7 для еще более прямого аргумента. - person Kerrek SB; 07.12.2013

Я думаю, что лучше избегать 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 потенциально оценивается и может использоваться для принудительной обработки ошибки в реализациях, которые не поддерживают передачу этого типа через ....

person Community    schedule 07.12.2013
comment
Существует черта is_null_pointer :-) - person Kerrek SB; 07.12.2013
comment
@KerrekSB Я не знал об этом, так что спасибо, но это не часть C ++ 11, а только грядущий C ++ 14. - person ; 07.12.2013
comment
Ваше решение предназначено исключительно для продвижения, что приятно; мой включает затухание как часть вызова переменной функции. - person Kerrek SB; 07.12.2013
comment
@KerrekSB Если вы имеете в виду преобразование функции/массива в указатель, оно также включено в мою версию. default_promote<int[3]>::type разрешается в int *. Если это не то, что вы имели в виду, не могли бы вы привести пример? - person ; 07.12.2013
comment
Как насчет ссылочных типов и типов функций? default_promote<const int &>, default_promote<int(int,int)>? - person Kerrek SB; 07.12.2013
comment
@KerrekSB Они работают и разрешаются в int и int(*)(int,int). - person ; 07.12.2013
comment
Ах хорошо. Тогда это здорово. Я думаю, что ?: лучше, чем +. - person Kerrek SB; 07.12.2013
comment
@KerrekSB Одна вещь, которую я обнаружил, сломалась в моей версии, это класс, который может преобразовываться в интегральный тип или из него (struct S с конструктором, принимающим int, а operator int() был тестом, который я использовал), исправленный добавлением проверки на std::is_scalar<T>. - person ; 07.12.2013
comment
Можете ли вы снова проверить перечисления без области видимости? Я получаю несоответствие, то есть default_promote<MyEnum> равно int, а default_promote<underlying_type<MyEnum>> равно unsigned int. - person Kerrek SB; 07.12.2013
comment
@KerrekSB Я понимаю, что ты имеешь в виду. Учитывая enum E0 { E0_0 };, std::underlying_type<E0>::type равно unsigned int. Я проверю, что происходит. - person ; 07.12.2013
comment
@KerrekSB FWIW, если я проверю с помощью gcc -fdump-tree-all единицу перевода, которая передает переменную E0 в список аргументов переменной ..., дампы показывают, что она преобразуется в int, даже если это не базовый тип. Я тоже проверю, что говорит стандарт. - person ; 07.12.2013
comment
@KerrekSB [conv.prom]p3 Значение prvalue типа перечисления с незаданной областью, базовый тип которого не является фиксированным (7.2), может быть преобразовано в значение prvalue первого из следующих типов, которые могут представлять все значения перечисления (т. е. значения в диапазоне от bmin до bmax, как описано в 7.2): int, unsigned int, long int, unsigned long int, long long int или unsigned long long int. Я думаю, что это правильно, перечисление с базовым типом unsigned int все еще может повышаться до int. - person ; 07.12.2013