¿Debería el estilo de sintaxis de tipo de retorno final convertirse en el predeterminado para los nuevos programas de C ++ 11? [cerrado]

92

C ++ 11 admite una nueva sintaxis de función:

auto func_name(int x, int y) -> int;

Actualmente esta función se declararía como:

int func_name(int x, int y);

El nuevo estilo no parece haber sido ampliamente adoptado todavía (digamos en el gcc stl)

Sin embargo, ¿debería preferirse este nuevo estilo en todas partes en los nuevos programas de C ++ 11, o sólo se utilizará cuando sea necesario?

Personalmente, prefiero el estilo antiguo cuando es posible, pero una base de código con estilos mixtos se ve bastante fea.

tenebroso
fuente
29
Está ahí principalmente para decltypelos argumentos.
Cat Plus Plus
lo que dice CatPlusPlus: no tiene mucho sentido usarlo en su ejemplo
stijn
@Cat Plus Plus Esto significa que deja las cosas como están en C ++ 03, a menos que necesite derivar el tipo de retorno.
mirk
1
Es feo tener que especificar "auto" delante de cada función. ¿Es esa la respuesta picante de C ++ a la "def" de Python?
Erik Aronesty

Respuestas:

110

Hay ciertos casos en los que debe utilizar un tipo de retorno final. En particular, un tipo de retorno lambda, si se especifica, debe especificarse mediante un tipo de retorno final. Además, si su tipo de retorno utiliza un decltypeque requiere que los nombres de los argumentos estén dentro del alcance, se debe usar un tipo de retorno final (sin embargo, generalmente se puede usar declval<T>para solucionar este último problema).

El tipo de retorno final tiene otras ventajas menores. Por ejemplo, considere una definición de función miembro no en línea utilizando la sintaxis de función tradicional:

struct my_awesome_type
{
    typedef std::vector<int> integer_sequence;

    integer_sequence get_integers() const;
}; 

my_awesome_type::integer_sequence my_awesome_type::get_integers() const
{
    // ...
}

Las definiciones de tipo de miembro no están dentro del alcance hasta que el nombre de la clase aparece antes ::get_integers, por lo que tenemos que repetir la calificación de la clase dos veces. Si usamos un tipo de retorno final, no necesitamos repetir el nombre del tipo:

auto my_awesome_type::get_integers() const -> integer_sequence
{
    // ...
}

En este ejemplo, no es tan importante, pero si tiene nombres de clase largos o funciones miembro de plantillas de clase que no están definidas en línea, entonces puede marcar una gran diferencia en la legibilidad.

En su sesión "Fresh Paint" en C ++ Now 2012, Alisdair Meredith señaló que si usa tipos de retorno finales de manera consistente, los nombres de todas sus funciones se alinean ordenadamente:

auto foo() -> int;
auto bar() -> really_long_typedef_name;

He usado tipos de retorno finales en todas partes en CxxReflect , por lo que si está buscando un ejemplo de cómo se ve el código usándolos de manera consistente, puede echar un vistazo allí (por ejemplo, la typeclase ).

James McNellis
fuente
1
No parece que haya un consenso todavía, pero es interesante ver CxxReflect con el nuevo estilo.
mirk
Hola James. Esta respuesta probablemente podría hacerse más precisa a la luz del estándar C ++ 14.
Drew Dormann
@DrewDormann ¿Qué agregarías / cambiarías?
underscore_d
La alineación es en realidad una gran ventaja, hasta el punto en que deseaba que hubiera una nueva palabra clave 'func' para reemplazar la sin sentido 'auto' aquí.
Johan Boulé
67

Además de lo que otros dijeron, el tipo de retorno final también permite usar this, que de otra manera no está permitido

struct A {
  std::vector<int> a;

  // OK, works as expected
  auto begin() const -> decltype(a.begin()) { return a.begin(); }

  // FAIL, does not work: "decltype(a.end())" will be "iterator", but 
  // the return statement returns "const_iterator"
  decltype(a.end()) end() const { return a.end(); }
};

En la segunda declaración, usamos el estilo tradicional. Sin embargo, dado thisque no está permitido en esa posición, el compilador no lo usa implícitamente. Entonces, a.end()usa el tipo declarado estáticamente de apara determinar qué endsobrecarga vector<int>va a llamar, que termina siendo la versión no constante.

Johannes Schaub - litb
fuente
2
Si bien esta es una demostración buena / ordenada del concepto (usando miembros en tipos de retorno), es gracioso ya que en C ++ 14 especificar un tipo es totalmente redundante en una definición en línea sin conversión; ahora podemos usar la deducción completa del tipo de devolución. : P
underscore_d
27

Otra ventaja es que la sintaxis del tipo de retorno al final puede ser más legible cuando la función devuelve un puntero a una función. Por ejemplo, compare

void (*get_func_on(int i))(int);

con

auto get_func_on(int i) -> void (*)(int);

Sin embargo, se puede argumentar que se puede lograr una mejor legibilidad simplemente introduciendo un alias de tipo para el puntero de función:

using FuncPtr = void (*)(int);
FuncPtr get_func_on(int i);
s3rvac
fuente
10

Vea este buen artículo: http://www.cprogramming.com/c++11/c++11-auto-decltype-return-value-after-function.html Muy buen ejemplo de cuándo usar esta sintaxis sin decltype en el juego :

class Person
{
public:
    enum PersonType { ADULT, CHILD, SENIOR };
    void setPersonType (PersonType person_type);
    PersonType getPersonType ();
private:
    PersonType _person_type;
};

auto Person::getPersonType () -> PersonType
{
    return _person_type;
}

Y una explicación brillante también robada del artículo de Alex Allain "Debido a que el valor de retorno va al final de la función, en lugar de antes, no es necesario agregar el alcance de la clase".

Compare con este posible caso cuando uno por accidente se olvida del alcance de la clase y, para un desastre mayor, se define otro PersonType en el alcance global:

typedef float PersonType; // just for even more trouble
/*missing: Person::*/
PersonType Person::getPersonType ()
{
    return _person_type;
}
PiotrNycz
fuente
7
No estoy seguro de que esto caiga en la categoría de "desastre": si el tipo es incorrecto, el código no se compilará. Los errores en tiempo de ejecución pueden tener consecuencias desastrosas; errores en tiempo de compilación, no tanto.
James McNellis
4
@JamesMcNellis compara la salida del compilador: prog.cpp:13:12: error: prototype for 'PersonType Person::getPersonType()' does not match any in class 'Person'vs. prog.cpp:13:1: error: 'PersonType' does not name a type El primer error del compilador es, al menos para mí, peor de entender.
PiotrNycz
Personalmente, no estoy de acuerdo, el segundo mensaje me resulta más difícil de leer y prefiero que la implementación se parezca a la declaración.
jrh