CppCoreGuidelines
CppCoreGuidelines copied to clipboard
When to use trailing return type syntax
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 thedecltype
expression would otherwise have to construct a type instance (e.g. usingstd::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 usingclass_name::some_member
.
- Similarly when
- 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).
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.
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();
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();
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.
if the caller is using
auto
as the return type and trying to use type hinting to suggest whatauto
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.
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; });
if the caller is using
auto
as the return type and trying to use type hinting to suggest whatauto
will beWhy 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
.
@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
The latter isn't valid C++
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 thanks for the fix of my 2nd one; though I disagree that using auto
is nicer.
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 …
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.