Предотвращает ли стандарт сужение преобразования литералов с достаточно малыми литеральными значениями в шаблонах переменных - PullRequest
3 голосов
/ 11 октября 2019

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

#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, как обойти эту ошибку || исправить этот нестандартный код?

Ответы [ 2 ]

2 голосов
/ 11 октября 2019

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

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

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

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

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

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

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

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

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

выделение мин

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

1 голос
/ 11 октября 2019

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

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

Ради интереса, я предлагаю вам способ (не очень практичный) передавать значения литерального типа в качестве аргументов функции (в некотором смысле), избегая проблемы сужения: вы можете получить значения 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>();
...