¿Cómo trato las advertencias de "discrepancia firmada / no firmada" (C4018)?

80

Trabajo con una gran cantidad de código de cálculo escrito en C ++ pensando en un alto rendimiento y una baja sobrecarga de memoria. Utiliza contenedores STL (principalmentevector ) mucho, e itera sobre esos contenedores casi en cada función.

El código iterativo se ve así:

for (int i = 0; i < things.size(); ++i)
{
    // ...
}

pero produce la falta de coincidencia firmada / no firmada advertencia de (C4018 en Visual Studio).

Reemplazarlo intcon algún unsignedtipo es un problema porque con frecuencia usamos pragmas OpenMP y requiere que el contador sea int.

Estoy a punto de suprimir las (cientos de) advertencias, pero me temo que me he perdido alguna solución elegante al problema.

En iteradores . Creo que los iteradores son geniales cuando se aplican en lugares apropiados. El código con el que estoy trabajando nunca cambiará los contenedores de acceso aleatorio en listo algo así (por lo que la iteración int iya es independiente del contenedor), y siempre necesitará el índice actual. Y todo el código adicional que necesita escribir (el iterador en sí y el índice) simplemente complica las cosas y ofusca la simplicidad del código subyacente.

Andrew T
fuente
1
¿Puede publicar un ejemplo en el que el pragma de OpenMP le impida usar un tipo sin firmar? De acuerdo con esto , debería funcionar para cualquier tipo de intergal, no solo int.
Billy ONeal
4
Creo que esta pregunta es mejor para stackoverflow.
bcsanches
1
inty std::vector<T>::size_typetambién pueden ser diferentes en tamaño y en firma. Por ejemplo, en un sistema LLP64 (como Windows de 64 bits), sizeof(int) == 4pero sizeof(std::vector<T>::size_type) == 8.
Adrian McCarthy
posible duplicado de stackoverflow.com/questions/8188401/…
CinCout

Respuestas:

60

Todo está en tu things.size()tipo. No lo es int, pero size_t(existe en C ++, no en C) que equivale a algún tipo sin signo "habitual", es decirunsigned int para x86_32.

El operador "menos" (<) no se puede aplicar a dos operandos de signo diferente. Simplemente no existen tales códigos de operación, y el estándar no especifica si el compilador puede realizar una conversión de signo implícita. Por lo tanto, solo trata el número firmado como sin firmar y emite esa advertencia.

Sería correcto escribirlo como

for (size_t i = 0; i < things.size(); ++i) { /**/ }

o incluso más rápido

for (size_t i = 0, ilen = things.size(); i < ilen; ++i) { /**/ }
Gigala
fuente
17
-1 no, no lo es size_t. Lo es std::vector< THING >::size_type.
Raedwald
8
@Raedwald: Si bien es técnicamente correcto, es difícil imaginar cómo una implementación compatible con los estándares podría terminar con diferentes tipos subyacentes para std::size_ty std::vector<T>::size_type.
Adrian McCarthy
4
¿Por qué ++ i se considera mejor? En bucles for, ¿no hay "ninguna" diferencia?
Shoaib
2
@ShoaibHaider, no importa en absoluto para las primitivas donde no se usa el valor de retorno. Sin embargo, para los tipos personalizados (donde el operador está sobrecargado), el incremento posterior es casi siempre menos eficiente (ya que tiene que hacer una copia del objeto antes de que se incremente). Los compiladores no pueden (necesariamente) optimizar para tipos personalizados. Entonces, la única ventaja es la consistencia (de tipos primitivos frente a tipos personalizados).
Kat
2
@zenith: Sí, tienes razón. Mi declaración es válida solo para el asignador predeterminado. Un asignador personalizado podría usar algo diferente a std :: size_t, pero creo que aún tendría que ser un tipo integral sin firmar, y probablemente no podría representar un rango más grande que std :: size_t, por lo que aún es seguro usar std :: size_t como el tipo de un índice de bucle.
Adrian McCarthy
13

Idealmente, usaría una construcción como esta en su lugar:

for (std::vector<your_type>::const_iterator i = things.begin(); i != things.end(); ++i)
{
  // if you ever need the distance, you may call std::distance
  // it won't cause any overhead because the compiler will likely optimize the call
  size_t distance = std::distance(things.begin(), i);
}

Esto tiene la gran ventaja de que su código de repente se convierte en un contenedor independiente.

Y con respecto a su problema, si alguna biblioteca que usa requiere que la use intdonde unsigned intencajaría mejor, su API es complicada. De todos modos, si está seguro de que intsiempre son positivos, puede hacer lo siguiente:

int int_distance = static_cast<int>(distance);

Lo que especificará claramente su intención para el compilador: ya no lo molestará con advertencias.

ereOn
fuente
1
Yo siempre necesito la distancia. Quizás static_cast<int>(things.size())podrían ser las soluciones, si no hay otras.
Andrew T
@Andrew: Si decide suprimir la advertencia, la mejor manera probablemente sería usar un pragma específico del compilador (en MSVC esto será a #pragma warning(push) #pragma warning(disable: 4018) /* ... function */ #pragma warning(pop)) en lugar de usar una conversión innecesaria. (Los yesos esconden errores legítimos, ¿de acuerdo?;))
Billy ONeal
No es verdad. Cast! = Conversión. La advertencia advierte sobre una conversión implícita permitida por el estándar que puede no ser segura. Un elenco, sin embargo, invita a conversiones explícitas a la fiesta que pueden ser más inseguras de lo que se habló originalmente en la advertencia. Estrictamente hablando, al menos en x86, no se producirá ninguna conversión: al procesador no le importa si está tratando una parte específica de la memoria como firmada o sin firmar, siempre que use las instrucciones correctas para trabajar con ella.
Billy ONeal
Cuando ser agnóstico al contenedor causa complejidad O (N ^ 2) (y en su ejemplo lo hace, ya que la distancia () es O (N) para la lista <>), no estoy tan seguro de que sea una ventaja :-(.
Liebre sin errores
@ No-BugsHare Ese es exactamente el punto: no podemos estar seguros. Si el OP tiene pocos elementos, probablemente sea genial. Si tiene millones de esos, probablemente no tanto. Solo la creación de perfiles puede decirlo al final, pero la buena noticia es que siempre se puede optimizar el código mantenible
2018
9

Si no puede / no usar iteradores y si no puede / no utilizar std::size_tpara el índice de bucle, hacer un .size()a intfunción de conversión de documentos que la asunción y realiza la conversión explícita para silenciar la advertencia del compilador.

#include <cassert>
#include <cstddef>
#include <limits>

// When using int loop indexes, use size_as_int(container) instead of
// container.size() in order to document the inherent assumption that the size
// of the container can be represented by an int.
template <typename ContainerType>
/* constexpr */ int size_as_int(const ContainerType &c) {
    const auto size = c.size();  // if no auto, use `typename ContainerType::size_type`
    assert(size <= static_cast<std::size_t>(std::numeric_limits<int>::max()));
    return static_cast<int>(size);
}

Luego escribes tus bucles así:

for (int i = 0; i < size_as_int(things); ++i) { ... }

Es casi seguro que la instanciación de esta plantilla de función estará incorporada. En las compilaciones de depuración, se comprobará la suposición. En las versiones de lanzamiento, no lo será y el código será tan rápido como si llamaras a size () directamente. Ninguna versión producirá una advertencia del compilador, y es solo una pequeña modificación del ciclo idiomático.

Si también desea detectar fallas de suposición en la versión de lanzamiento, puede reemplazar la aserción con una declaración if que arroje algo como std::out_of_range("container size exceeds range of int") .

Tenga en cuenta que esto resuelve tanto la comparación firmada / no firmada como el problema potencial sizeof(int)! = sizeof(Container::size_type). Puede dejar todas sus advertencias habilitadas y usarlas para detectar errores reales en otras partes de su código.

Adrian McCarthy
fuente
6

Puedes usar:

  1. size_t tipo, para eliminar mensajes de advertencia
  2. iteradores + distancia (como son la primera pista)
  3. solo iteradores
  4. objeto de función

Por ejemplo:

// simple class who output his value
class ConsoleOutput
{
public:
  ConsoleOutput(int value):m_value(value) { }
  int Value() const { return m_value; }
private:
  int m_value;
};

// functional object
class Predicat
{
public:
  void operator()(ConsoleOutput const& item)
  {
    std::cout << item.Value() << std::endl;
  }
};

void main()
{
  // fill list
  std::vector<ConsoleOutput> list;
  list.push_back(ConsoleOutput(1));
  list.push_back(ConsoleOutput(8));

  // 1) using size_t
  for (size_t i = 0; i < list.size(); ++i)
  {
    std::cout << list.at(i).Value() << std::endl;
  }

  // 2) iterators + distance, for std::distance only non const iterators
  std::vector<ConsoleOutput>::iterator itDistance = list.begin(), endDistance = list.end();
  for ( ; itDistance != endDistance; ++itDistance)
  {
    // int or size_t
    int const position = static_cast<int>(std::distance(list.begin(), itDistance));
    std::cout << list.at(position).Value() << std::endl;
  }

  // 3) iterators
  std::vector<ConsoleOutput>::const_iterator it = list.begin(), end = list.end();
  for ( ; it != end; ++it)
  {
    std::cout << (*it).Value() << std::endl;
  }
  // 4) functional objects
  std::for_each(list.begin(), list.end(), Predicat());
}

fuente
3

También puedo proponer la siguiente solución para C ++ 11.

for (auto p = 0U; p < sys.size(); p++) {

}

(C ++ no es lo suficientemente inteligente para auto p = 0, así que tengo que poner p = 0U ....)

Stepan Yakovenko
fuente
1
+1 para C ++ 11. A menos que haya una buena razón por la que no pueda usar C ++ 11, creo que es mejor usar las nuevas funciones ... están destinadas a ser de gran ayuda. Y definitivamente úselo for (auto thing : vector_of_things)si no necesita el índice.
parker.sikand
Pero eso solo resuelve el problema de la firma. No ayuda si size()devuelve un tipo más grande que unsigned int, que es extremadamente común.
Adrian McCarthy
3

Te daré una mejor idea

for(decltype(things.size()) i = 0; i < things.size(); i++){
                   //...
}

decltype es

Inspecciona el tipo declarado de una entidad o la categoría de tipo y valor de una expresión.

Entonces, deduce el tipo de things.size()y iserá un tipo igual que things.size(). Por lo tanto, i < things.size()se ejecutará sin previo aviso.

Daniel kim
fuente
0

Tuve un problema similar. El uso de size_t no funcionaba. Probé el otro que funcionó para mí. (como a continuación)

for(int i = things.size()-1;i>=0;i--)
{
 //...
}
Karthik_elan
fuente
0

Yo solo haría

int pnSize = primeNumber.size();
for (int i = 0; i < pnSize; i++)
    cout << primeNumber[i] << ' ';
Don laringe
fuente