Предотвращает ли стандарт сужение преобразования литерала с достаточно малыми литеральными значениями в вариативных шаблонах?

Вот минимальный пример:

#include <array>

template <class... T>
constexpr std::array<unsigned char, sizeof...(T)> act(T... aArgs)
{
    return std::array<unsigned char, sizeof...(T)>{aArgs...};
}

int main()
{
    act(5, 5);
}

РЕДАКТИРОВАТЬ Где GCC и Clang могут скомпилировать этот фрагмент без жалоб Нет, не могут.

Последний MSVC терпит неудачу с:

<source>(6): error C2397: conversion from 'int' to '_Ty' requires a narrowing conversion

    with

    [

        _Ty=unsigned char

    ]

См.: https://godbolt.org/z/1PmeLk.


  • Поскольку в этой ситуации у компилятора есть все необходимое для статической проверки того, что вызов act(5, 5) не приводит к переполнению для предоставленных значений, соответствует ли стандартному поведению сбой в этом коде?

Бонусный вопрос:

  • Поскольку нет суффикса литерала для получения литерала без знака char, как обойти эту ошибку || исправить этот нестандартный код?

person Ad N    schedule 11.10.2019    source источник
comment
Какой gcc вы используете? Я получаю предупреждения/ошибки: godbolt.org/z/FhnE48   -  person NathanOliver    schedule 11.10.2019
comment
Спасибо за это разъяснение, мой Apple Clang и сопутствующий gcc устарели... Я отредактировал вопрос   -  person Ad N    schedule 11.10.2019
comment
Ошибки компилятора для меня, используя clang. Можете свернуть свой собственный буквальный суффикс, inline constexpr unsigned char operator ""_uchar( unsigned long long arg ) noexcept { return static_cast< unsigned char >( arg ); }   -  person Eljay    schedule 11.10.2019
comment
Обходной путь static_cast<unsigned char>(args)....   -  person super    schedule 11.10.2019


Ответы (2)


Поскольку в этой ситуации у компилятора есть все необходимое для статической проверки того, что вызов act(5, 5) не переполняется для предоставленных значений, соответствует ли стандартному поведению сбой в этом коде?

Да. Компилятору разрешено выполнять сужающее преобразование только в том случае, если он может гарантировать, что значение может быть представлено в более узком типе. Если у тебя есть

std::array<unsigned char, 2> foo = {5, 127};

тогда это будет нормально, потому что компилятор знает, что 5 и 127 можно представить. Ваш случай, однако, не то же самое. Вы выполняете свою инициализацию внутри функции, а внутри функции {aArgs...} нет такой же гарантии, поскольку это не постоянное выражение (переменные, переданные в функцию, не являются постоянными выражениями). Из-за этого компилятор не может во всех случаях доказать, что он будет действительным, поэтому он выдает предупреждение/ошибку.

Поскольку нет суффикса литерала для получения литерала без знака char, как обойти эту ошибку || исправить этот нестандартный код?

Вы можете сделать так, чтобы ваш собственный пользователь определял литерал, чтобы создавать неподписанные символы, например

inline constexpr unsigned char operator ""_uc( unsigned long long arg ) noexcept 
{ 
    return static_cast< unsigned char >( arg ); 
}
//...
act(5_uc, 5_uc);

или вы можете просто передать им, как

act((unsigned char)5, (unsigned char)5);

Фактическую формулировку, которая охватывает этот случай, можно найти в [dcl.init] /7

Сужающее преобразование — это неявное преобразование [...]

  • из целочисленного типа или типа перечисления с незаданной областью в тип с плавающей запятой, за исключением где источником является константное выражение и фактическое значение после преобразования будет соответствовать целевому типу и будет создавать исходное значение при преобразовании вернуться к исходному типу [...]

выделено мной

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

person NathanOliver    schedule 11.10.2019
comment
Спасибо за ваш ответ. Тем не менее, предоставленные значения LiteralType передаются в качестве аргумента constexpr шаблонной функции, определение которой доступно в модуле компиляции, вызывающем ошибки. Я согласен с вами в том, что компилятор не может доказать, что предоставленное значение будет действительным во всех случаях, но определенно кажется, что у него есть все необходимое для этого в этом случае. - person Ad N; 11.10.2019

Бонусный вопрос:

Поскольку нет суффикса литерала для получения литерала без знака char, как обойти эту ошибку || исправить этот нестандартный код?

Ради интереса я предлагаю вам способ (не совсем практичный) передать значения ваших литеральных типов в качестве аргументов функции (в некотором смысле), избегая проблемы сужения: вы можете получить значения args через std::integral_constant

template <typename ... T, T ... args>
constexpr std::array<unsigned char, sizeof...(T)>
   act (std::integral_constant<T, args>...)
 {
   return std::array<unsigned char, sizeof...(T)>{args...};
 }

и назовите его следующим образом

act(std::integral_constant<int, 5>{}, 
    std::integral_constant<long long, 5ll>{});

Таким образом, значение args является значением шаблона, поэтому компилятору гарантируется, что конкретная реализация функции безопасна с точки зрения сужения (очевидно, вызов ее с адекватными значениями).

Если вы можете использовать C++17, вы получите тот же результат (более простым и практичным способом), просто передав аргументы как значения шаблона auto.

template <auto ... args>
constexpr auto act ()
 {
   return std::array<unsigned char, sizeof...(args)>{args...};
 }

// ...

act<5, 5ll>();
person max66    schedule 11.10.2019