Open ntrel opened 1 year ago
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. 🤌
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.
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.
It actually isn't, before it is known well-formed. Given the fact that C++ is a language with horrible states not only in the parser for the syntactic grammar, this will make sense. Practical C++ implementations usually assume it well-formed and try. Nor do humans better here in essence.
The infix declarators of C can suffer in the similar way, too: the "right-left" rules will not work as the precise rules from the formal grammar when the to-be-declared identifier is unknown (due to the allowence to mix the abstract declarators and non-abstract declarators together as parts of a single declarator).
Such context-sensitivity and semantic-sensitivity is a result of the decision from the design of C declarators. Most other PLs use trailing/suffix syntaxes for type annotations in declarations (if any) all the time. The prefix/infix syntaxes in C is already a hack comparing to the tradition, and C++ has just added more hacks (to allow them coexisting) here.
In general, non-infix return syntaxes of explicit type annotation are more friendly to thinking in extrinsic typing:
On the opposite side, type inference exposes the duality as this process with a known set of typing rules, established by intrinsic typing design from a statically typed language. Type inference does not work in extrinsic typing thinking like this "in-place" way, as it transforms the potentially inferable in IR instead of terms directly represented in the source language. It is generally more difficult to perform this in human brains as it requires more memory (both for the IR representing typing environment and precise set of typing rules). Infix declaration is just one more difficult for this because the contextual noise will make every step a potentially non-trivial code transformation to keep the intermediate result always syntactically and semantically correct (consistent both to the the intended meaning of the original code and the language rules).
Moreover, although statically typed languages do not formally build the semantics for a typed program like this, the language itself is built (and evoluted) in this meta way, as the classical work from extending UTLC to STLC and languages with many more powerful type systems. An optimizing compiler for a dynamically typed (or more precisely, latent typed) language can also have the code transformation in any invented type systems not implied by the source language in the IR of its optimization passes on-the-fly, in essentially the same manner of extending a language to a more statically typed one by hand.
Despite to the meta-level techniques, the capability of being extrinsic is quite crucial for users already with experience of latent typing languages, esp. in the sense of teachability. Weakening the type annotation to a hint is the easiest way to make languages with different type disciplines handled in one same common way, without the requirements of knowing how to do code transformation in meta level (either typing algorithms in extrinsic typing, or type inference in intrinsic typing).
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.
I admit I am not a fan of trailing return type syntax of C++, and I come here just because I want to find some criteria of "nicer", before I can make some improvement to current clang-tidy
's [fuchsia-trailing-return]
which will kill all instances out of lambda-expressions and decltype
contexts. This is far from "nice" (it even does not detect the necessity).
Inspired by: https://blog.petrzemek.net/2017/01/17/pros-and-cons-of-alternative-function-syntax-in-cpp/
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).class_name::
for the return type.decltype
is used for a class method return type with an expression usingclass_name::some_member
.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).