CppCoreGuidelines icon indicating copy to clipboard operation
CppCoreGuidelines copied to clipboard

When to use trailing return type syntax

Open ntrel opened this issue 1 year ago • 15 comments

Inspired by: https://blog.petrzemek.net/2017/01/17/pros-and-cons-of-alternative-function-syntax-in-cpp/

  • When using decltype, as then the return type is free to use parameter names too. If this is thought to be too strict, then use trailing return when the decltype expression would otherwise have to construct a type instance (e.g. using std::declval) when there is also a function parameter of that type which could be used instead (which would be easier to read).
  • When defining a method outside of the class declaration, and the return type is a type alias defined inside the same class as the method. This avoids repeating class_name:: for the return type.
    • Similarly when decltype is used for a class method return type with an expression using class_name::some_member.
  • When the function returns a function pointer. This is much easier to read.

More generally, it would be good to recommend trailing return whenever the return type has more than one alpha-numeric token, so the function name is easy to read quickly. (This would obviate the need for the first two rules above).

ntrel avatar Apr 17 '23 13:04 ntrel

I think the rules for this would be pretty complicated. For example, if you use std::enable_if_t in the return type, or some other traits or forms of doing SFINAE, trailing return types make sense due to the sheer length.

Trailing return types in general are a weird hack in the language, and

auto main() -> int

is something that almost no one teaches, and that is also really counter-intuitive at first glance (why is it auto?!).

I think trailing return types have been, and probably will always be more of a desperate resort, rather than a default.

Eisenwave avatar Jul 26 '23 12:07 Eisenwave

But functions names alignment is so nice. 🤌

https://github.com/jship/CpperoMQ/blob/7a685b078ab172d8ad8c3ce6910c2f2e9f7e0d1e/include/CpperoMQ/Mixins/SendingSocket.hpp#L46C1-L46C1

Perhaps not the best example, but imagine something like this. Also Rust, Carbon etc. go with putting return type on the right, they don't have auto though.

auto foo1() -> int;
auto foo2() -> double;
auto foo3() -> std::shared_ptr<TcpConnection>;
auto foo4() -> Point2D;
auto foo5() -> std::unique_ptr<Point2D>;

int foo1();
double foo2();
std::shared_ptr<TcpConnection> foo3();
Point2D foo4();
std::unique_ptr<Point2D> foo5();

claimred avatar Jul 26 '23 13:07 claimred

But functions names alignment is so nice. :pinched_fingers:

You don't need trailing return types for that.

int
foo1();

double
foo2();

std::shared_ptr<TcpConnection>
foo3();

Point2D
foo4();

std::unique_ptr<Point2D>
foo5();

jwakely avatar Jul 26 '23 14:07 jwakely

Trailing return types are used in languages like Python and JavaScript as Type Hints what will be returned since they do not have type enforcement. For C++ it makes no sense, and if the caller is using auto as the return type and trying to use type hinting to suggest what auto will be then I would say that the API is wrong and that auto should be removed in favor of the actual type being returned.

BenjamenMeyer avatar Jul 26 '23 16:07 BenjamenMeyer

if the caller is using auto as the return type and trying to use type hinting to suggest what auto will be

Why would anybody be trying to use it to mean something it doesn't mean? A trailing return type isn't a hint, it is the return type.

jwakely avatar Jul 26 '23 17:07 jwakely

I think trailing return types are sometimes unavoidable when the order of places a template argument is referenced decides on whether it can easily inferred into the type to use …

template<typename T>
auto foo(T a, T b) -> std::vector<T>;

That said, IMHO the use should be kept to a minimum and best be reserved for situations where there's no other (easy) option:

template<typename T>
auto bar(T x) -> decltype([=](){ return x + x; });

BenBE avatar Jul 26 '23 17:07 BenBE

if the caller is using auto as the return type and trying to use type hinting to suggest what auto will be

Why would anybody be trying to use it to mean something it doesn't mean? A trailing return type isn't a hint, it is the return type.

@jwakely 100% agree. I'm pointing to what people coming from other languages where type hinting is used would see it; but it's also 100% why C++ doesn't need it. Just use the type and skip using auto.

BenjamenMeyer avatar Jul 26 '23 18:07 BenjamenMeyer

@BenBE auto is completely unnecessary in those case and just complicates it

template<typename T>
auto foo(T a, T b) -> std::vector<T>;

Or just do:

template<typename T>
std::vector<T> foo(T a, T b);
template<typename T>
auto bar(T x) -> decltype([=](){ return x + x; });

Or rather:

template<typename T>
T bar(T x) -> decltype([=](){ return x + x; });

EDIT: Yeah - messed up the second one; see https://github.com/isocpp/CppCoreGuidelines/issues/2066#issuecomment-1652287140 for the proper version

BenjamenMeyer avatar Jul 26 '23 18:07 BenjamenMeyer

The latter isn't valid C++

jwakely avatar Jul 26 '23 18:07 jwakely

template<typename T>
decltype(std::declval<T&>() + std::declval<T&>()) fun(T x);

vs

template<typename T>
auto fun(T x) -> decltype(x + x);

Is auto necessary here? No, but it's arguably nicer.

Edit: changed function name to avoid confusion with the bar above.

jwakely avatar Jul 26 '23 18:07 jwakely

@jwakely thanks for the fix of my 2nd one; though I disagree that using auto is nicer.

BenjamenMeyer avatar Jul 26 '23 21:07 BenjamenMeyer

Which is not quite the same as in my example of bar which returns a lambda instead of the type that T.operator+(T) would return … Nonetheless, in practical code I'd rather advise people to return std::function<T()> instead …

BenBE avatar Jul 26 '23 21:07 BenBE

Right, I wasn't trying to show the same as your lambda, just another (simpler) case where the trailing return type allows reuse of parameter names. I don't think your lambda example is valid C++ either, is it? The lambda in the return type would not be the same as the lambda in the function body, so you can't do that.

jwakely avatar Jul 26 '23 21:07 jwakely