C — Неявное преобразование unsigned long long в signed long long ?

#c

#c

Вопрос:

У меня довольно странное предупреждение, о котором сообщает clang-tidy 12.0.1. В следующем коде:

 #include <vector>

int main()
{
    std::vector<int> v1;

    const auto a = v1.begin()   v1.size();

    return 0;
}
 

Я вижу, что это предупреждение срабатывает:

 error: narrowing conversion from 'std::vector<int>::size_type' (aka 'unsigned long long') to signed type 'std::_Vector_iterator<std::_Vector_val<std::_Simple_types<int>>>::difference_type' (aka 'long long') is implementation-defined [bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions,-warnings-as-errors]
    const auto a = v1.begin()   v1.size();
                                ^
 

Насколько я понимаю, при работе с двумя целыми числами с одинаковым размером, но разной подписью целое число со знаком преобразуется в unsigned, а не наоборот. Я что-то здесь упускаю?

Комментарии:

1. Здесь вы не работаете с двумя целыми числами. Вы передаете целочисленный аргумент без знака для параметра целочисленной функции со знаком.

2. @Evg Я отредактировал свой вопрос с помощью более минималистичного примера. О какой функции вы говорите?

3. переполнение со знаком — это UB

4. evg говорит, что вы вызываете «iterator::operator (long long)», где «long long» — это разностный тип итератора. Таким образом, ваш std::size_t неявно преобразуется в signed long long, и вы получаете это предупреждение clang tidy.

Ответ №1:

Начиная с C 20 простым решением является использование std::sszie :

 const auto a = v1.begin()   std::ssize(v1);
 

Комментарии:

1. Или использовать v1.end() . 😉

Ответ №2:

Чтобы показать фактические задействованные типы :

 // Operator  accepts difference type
// https://en.cppreference.com/w/cpp/iterator/move_iterator/operator_arith
// constexpr move_iterator operator ( difference_type n ) const;

#include <type_traits>
#include <vector>
#include <iterator>

int main()
{
    std::vector<int> v1;

    auto a = v1.begin();
    auto d = v1.size();

    // the difference type for the iterator is a "long long"
    static_assert(std::is_same_v<long long, decltype(a)::difference_type>);

    // the type for size is not the same as the difference type
    static_assert(!std::is_same_v<decltype(d), decltype(a)::difference_type>);
    
    // it is std::size_t
    static_assert(std::is_same_v<decltype(d), std::size_t>);
    
    return 0;
}
 

Комментарии:

1. Понятно. Удивительно, как C может превратить даже самые простые задачи в сложные кошмары.

2. Если вы думаете, что это кошмар, вы еще не касались инициализации. 😉 С другой стороны, правила по большей части довольно понятны и их можно посмотреть. И я думаю, что это язык, на котором вы можете иметь очень высокий уровень абстракции, получать низкоуровневый контроль, когда это необходимо, и где вы можете получить детерминированное поведение (если вы остаетесь в стороне от UB).