ChatGPT解决这个技术问题 Extra ChatGPT

When should I use C++14 automatic return type deduction?

With GCC 4.8.0 released, we have a compiler that supports automatic return type deduction, part of C++14. With -std=c++1y, I can do this:

auto foo() { //deduced to be int
    return 5;
}

My question is: When should I use this feature? When is it necessary and when does it make code cleaner?

Scenario 1

The first scenario I can think of is whenever possible. Every function that can be written this way should be. The problem with this is that it might not always make the code more readable.

Scenario 2

The next scenario is to avoid more complex return types. As a very light example:

template<typename T, typename U>
auto add(T t, U u) { //almost deduced as decltype(t + u): decltype(auto) would
    return t + u;
}

I don't believe that would ever really be a problem, though I guess having the return type explicitly depend on the parameters could be clearer in some cases.

Scenario 3

Next, to prevent redundancy:

auto foo() {
    std::vector<std::map<std::pair<int, double>, int>> ret;
    //fill ret in with stuff
    return ret;
}

In C++11, we can sometimes just return {5, 6, 7}; in place of a vector, but that doesn't always work out and we need to specify the type in both the function header and the function body. This is purely redundant, and automatic return type deduction saves us from that redundancy.

Scenario 4

Finally, it can be used in place of very simple functions:

auto position() {
    return pos_;
}

auto area() {
    return length_ * width_;
}

Sometimes, though, we might look at the function, wanting to know the exact type, and if it isn't provided there, we have to go to another point in the code, like where pos_ is declared.

Conclusion

With those scenarios laid out, which of them actually prove to be a situation where this feature is useful in making the code cleaner? What about scenarios I have neglected to mention here? What precautions should I take before using this feature so that it doesn't bite me later? Is there anything new this feature brings to the table that isn't possible without it?

Note that the multiple questions are meant to be an aid in finding perspectives from which to answer this.

Wonderful question! While you're asking which scenarios make the code "better", I'm also wondering which scenarios will make it worse.
@DrewDormann, That's what I'm wondering as well. I like to make use of new features, but knowing when to use them and when not to is very important. There's a period of time when new features arise that we take to figure this out, so let's do it now so that we're ready for when it comes officially :)
@NicolBolas, Perhaps, but the fact that it's in an actual release of a compiler now would be enough for people to start using it in personal code (it definitely has to be kept away from projects at this point). I'm one of those people who likes using the newest possible features in my own code, and while I don't know how well the proposal is going with the committee, I figure the fact that it's the first included in this new option says something. It might be better left for later, or (I don't know how well it would work) revived when we know for sure it's coming.
@NicolBolas, If it helps, it's been adopted now :p
The current answers don't seem to mention that replacing ->decltype(t+u) with auto deduction kills SFINAE.

S
Steve Jessop

C++11 raises similar questions: when to use return type deduction in lambdas, and when to use auto variables.

The traditional answer to the question in C and C++03 has been "across statement boundaries we make types explicit, within expressions they are usually implicit but we can make them explicit with casts". C++11 and C++1y introduce type deduction tools so that you can leave out the type in new places.

Sorry, but you're not going to solve this up front by making general rules. You need to look at particular code, and decide for yourself whether or not it aids readability to specify types all over the place: is it better for your code to say, "the type of this thing is X", or is it better for your code to say, "the type of this thing is irrelevant to understanding this part of the code: the compiler needs to know and we could probably work it out but we don't need to say it here"?

Since "readability" is not objectively defined[*], and furthermore it varies by reader, you have a responsibility as the author/editor of a piece of code that cannot be wholly satisfied by a style guide. Even to the extent that a style guide does specify norms, different people will prefer different norms and will tend to find anything unfamiliar to be "less readable". So the readability of a particular proposed style rule can often only be judged in the context of the other style rules in place.

All of your scenarios (even the first) will find use for somebody's coding style. Personally I find the second to be the most compelling use case, but even so I anticipate that it will depend on your documentation tools. It's not very helpful to see documented that the return type of a function template is auto, whereas seeing it documented as decltype(t+u) creates a published interface you can (hopefully) rely on.

[*] Occasionally someone tries to make some objective measurements. To the small extent that anyone ever comes up with any statistically significant and generally-applicable results, they are completely ignored by working programmers, in favour of the author's instincts of what is "readable".


Good point with the connections to lambdas (though this allows for more complex function bodies). The main thing is that it is a newer feature, and I'm still trying to balance the pros and cons of each use case. To do this, it's helpful to see the reasons behind why it would be used for what so that I, myself, can discover why I prefer what I do. I might be overthinking it, but that's how I am.
@chris: like I say, I think it all comes down to the same thing. Is it better for your code to say, "the type of this thing is X", or is it better for your code to say, "the type of this thing is irrelevant, the compiler needs to know and we could probably work it out but we don't need to say it". When we write 1.0 + 27U we're asserting the latter, when we write (double)1.0 + (double)27U we're asserting the former. Simplicity of the function, degree of duplication, avoiding decltype might all contribute to that but none is going to be reliably decisive.
Is it better for your code to say, "the type of this thing is X", or is it better for your code to say, "the type of this thing is irrelevant, the compiler needs to know and we could probably work it out but we don't need to say it". - That sentence is exactly along the lines of what I'm looking for. I'll take that into consideration as I come across options of using this feature, and auto in general.
I would like to add that IDEs might alleviate the "readability problem". Take Visual Studio, for example: If you hover over the auto keyword, it will display the actual return type.
@andreee: that's true within limits. If a type has many aliases then knowing the actual type isn't always as useful as you'd hope. For instance an iterator type might be int*, but what's actually significant, if anything, is that the reason it's int* is because that's what std::vector<int>::iterator_type is with your current build options!
M
Morwenn

Generally speaking, the function return type is of great help to document a function. The user will know what is expected. However, there is one case where I think it could be nice to drop that return type to avoid redundancy. Here is an example:

template<typename F, typename Tuple, int... I>
  auto
  apply_(F&& f, Tuple&& args, int_seq<I...>) ->
  decltype(std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...))
  {
    return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...);
  }

template<typename F, typename Tuple,
         typename Indices = make_int_seq<std::tuple_size<Tuple>::value>>
  auto
  apply(F&& f, Tuple&& args) ->
  decltype(apply_(std::forward<F>(f), std::forward<Tuple>(args), Indices()))
  {
    return apply_(std::forward<F>(f), std::forward<Tuple>(args), Indices());
  }

This example is taken from the official committee paper N3493. The purpose of the function apply is to forward the elements of a std::tuple to a function and return the result. The int_seq and make_int_seq are only part of the implementation, and will probably only confuse any user trying to understand what it does.

As you can see, the return type is nothing more than a decltype of the returned expression. Moreover, apply_ not being meant to be seen by the users, I am not sure of the usefulness of documenting its return type when it's more or less the same as apply's one. I think that, in this particular case, dropping the return type makes the function more readable. Note that this very return type has actually been dropped and replaced by decltype(auto) in the proposal to add apply to the standard, N3915 (also note that my original answer predates this paper):

template <typename F, typename Tuple, size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, index_sequence<I...>) {
    return forward<F>(f)(get<I>(forward<Tuple>(t))...);
}

template <typename F, typename Tuple>
decltype(auto) apply(F&& f, Tuple&& t) {
    using Indices = make_index_sequence<tuple_size<decay_t<Tuple>>::value>;
    return apply_impl(forward<F>(f), forward<Tuple>(t), Indices{});
}

However, most of the time, it is better to keep that return type. In the particular case that I described above, the return type is rather unreadable and a potential user won't gain anything from knowing it. A good documentation with examples will be far more useful.

Another thing that hasn't been mentioned yet: while declype(t+u) allows to use expression SFINAE, decltype(auto) does not (even though there is a proposal to change this behaviour). Take for example a foobar function that will call a type's foo member function if it exists or call the type's bar member function if it exists, and assume that a class always has exacty foo or bar but neither both at once:

struct X
{
    void foo() const { std::cout << "foo\n"; }
};

struct Y
{
    void bar() const { std::cout << "bar\n"; }
};

template<typename C> 
auto foobar(const C& c) -> decltype(c.foo())
{
    return c.foo();
}

template<typename C> 
auto foobar(const C& c) -> decltype(c.bar())
{
    return c.bar();
}

Calling foobar on an instance of X will display foo while calling foobar on an instance of Y will display bar. If you use the automatic return type deduction instead (with or without decltype(auto)), you won't get expression SFINAE and calling foobar on an instance of either X or Y will trigger a compile-time error.


G
Gabe Sechan

It's never necessary. As to when you should- you're going to get a lot of different answers about that. I'd say not at all until its actually an accepted part of the standard and well supported by the majority of major compilers in the same way.

Beyond that, its going to be a religious argument. I'd personally say never- putting in the actual return type makes code clearer, is far easier for maintenance (I can look at a function's signature and know what it returns vs actually having to read the code), and it removes the possibility that you think it should return one type and the compiler thinks another causing problems (as has happened with every scripting language I've ever used). I think auto was a giant mistake and it will cause orders of magnitude more pain than help. Others will say you should use it all the time, as it fits their philosophy of programming. At any rate, this is way out of scope for this site.


I do agree that people with different backgrounds will have different opinions on this, but I'm hoping that this comes to a C++-ish conclusion. In (nearly) every case, it's inexcusable to abuse language features to try to make it into another, such as using #defines to turn C++ into VB. Features generally have a good concensus within the mindset of the language on what is proper use and what is not, according to what that language's programmers are accustomed to. The same feature may be present in multiple languages, but each has its own guidelines on the use of it.
Some features have a good consensus. Many don't. I know a lot of programmers who think the majority of Boost is garbage that shouldn't be used. I also know some who think its the greatest thing to happen to C++. In either case I think there are some interesting discussions to be had on it, but its really an exact example of the close as not constructive option on this site.
No, I completely disagree with you, and I think auto is pure blessing. It removes a lot of redundancy. It is simply a pain to repeat the return type sometimes. If you want to return a lambda, it can be even impossible without storing the result into a std::function, which may incur some overhead.
There's at least one situation where it's all but entirely necessary. Suppose you need to call functions, then log the results before returning them, and the functions don't necessarily all have the same return type. If you do so via a logging function that takes functions and their arguments as parameters, then you'll need to declare the logging function's return type auto so it always matches the return type of the passed function.
[There is technically another way to do this, but it uses template magic to do basically the exact same thing , and still needs the logging function to be auto for trailing return type.]
C
Community

It's got nothing to do with the simplicity of the function (as a now-deleted duplicate of this question supposed).

Either the return type is fixed (don't use auto), or dependent in a complex way on a template parameter (use auto in most cases, paired with decltype when there are multiple return points).


Yes, auto should be used where the type is uncertain/variable (because templates, usually). If the type is a known thing put that thing. If that thing is clumsy or inconveniently large, use a typedef/using.
k
kaiser

I want to provide an example where return type auto is perfect:

Imagine you want to create a short alias for a long subsequent function call. With auto you don't need to take care of the original return type (maybe it will change in future) and the user can click the original function to get the real return type:

inline auto CreateEntity() { return GetContext()->GetEntityManager()->CreateEntity(); }

PS: Depends on this question.


J
Jim Wood

Consider a real production environment: many functions and unit tests all interdependent on the return type of foo(). Now suppose that the return type needs to change for whatever reason.

If the return type is auto everywhere, and callers to foo() and related functions use auto when getting the returned value, the changes that need to be made are minimal. If not, this could mean hours of extremely tedious and error-prone work.

As a real-world example, I was asked to change a module from using raw pointers everywhere to smart pointers. Fixing the unit tests was more painful than the actual code.

While there are other ways this could be handled, the use of auto return types seems like a good fit.


On those cases, wouldn't it be better to write decltype(foo())?
Personally, I feel typedefs and alias declarations (i.e. using type declaration) are better in these cases, specially when they are class-scoped.
S
Serve Laurijssen

For scenario 3 I would turn the return type of function signature with the local variable to be returned around. It would make it clearer for client programmers hat the function returns. Like this:

Scenario 3 To prevent redundancy:

std::vector<std::map<std::pair<int, double>, int>> foo() {
    decltype(foo()) ret;
    return ret;
}

Yes, it has no auto keyword but the principal is the same to prevent redundancy and give programmers who dont have access to the source an easier time.


IMO the better fix for this is to provide a domain-specific name for the concept of whatever is intended to be represented as a vector<map<pair<int,double>,int> and then use that.