Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't we have automatically deduced return types?

Tags:

c++

c++11

Recently I was working a friend who wanted to make C++ more Haskell-y, and we wanted a function that's basically like this:

auto sum(auto a, auto b) {     return a + b; } 

Apparently I can't use auto as a parameter type, so I changed it to this:

template<class A, class B> auto sum(A a, B b) {     return a + b; } 

But that doesn't work either. What we eventually realized we need this:

template<class A, class B> auto sum(A a, B b) -> decltype(a + b) {     return a + b; } 

So my question is, what's the point? Isn't decltype just repeating information, since the compiler can just look at the return statement?

I considered that maybe it's needed so we can just include a header file:

template<class A, class B> auto sum(A a, B b) -> decltype(a + b); 

... but we can't use templates like that anyway.

The other thing I considered was that it might be easier for the compiler, but it seems like it would actually be harder.

Case 1: With decltype

  • Figure out the type of the decltype statement
  • Figure out the types of any return values
  • See if they match

Case 2: Without decltype

  • Figure out the types of any return values
  • See if they match

So with those things in mind, what's the point of the trailing return type with decltype?

like image 337
Brendan Long Avatar asked Sep 28 '11 21:09

Brendan Long


People also ask

Can return type be auto?

In C++14, you can just use auto as a return type.

What is c++ type deduction?

Type inference or deduction refers to the automatic detection of the data type of an expression in a programming language. It is a feature present in some strongly statically typed languages. In C++, the auto keyword(added in C++ 11) is used for automatic type deduction.


2 Answers

Well - time passed since the original question was asked and the answer now is that you can!

Yes, it is true that the question is tagged C++11 - with which you still cannot do what the OP is asking for. But it's worthwhile to show what is doable with C++14 and later.

Since C++14 this is valid:

template<class A, class B> auto sum(A a, B b) {     return a + b; } 

And since C++20 this is also valid:

auto sum(auto a, auto b) {     return a + b; } 

The following is the C++11 answer, kept here for historical reasons, with some comments from the future (C++14 and later):

What if we have the following:

template<class A, class B, class C> auto sum(A a, B b, C c) {    if (rand () == 0) return a + b;     // do something else...      return a + c; } 

.. where a + b and a + c expressions yield different type of results. What should compiler decide to put as a return type for that function and why? This case is already covered by C++11 lambdas which allow to omit the return type as long as return statements can be deduced to the same type (NB standard quote needed, some sources claim only one return expression is allowed and that this is a gcc glitch).


A note from the future (C++14 and on): the example above is still not valid, you may only have a single possible return type. However if there are different return types but the actual return type can be deduced at compile type, then we have two different functions, which is valid. The following for example is valid since C++17:

template<class A, class B, class C> auto sum(A a, B b, C c) {     if constexpr(std::is_same_v<A, B>) return a + b;     else return a + c; }  int main() {     auto a1 = sum(1, 2l, 3.5); // 4.5     auto a2 = sum(1, 2, 3.5); // 3 } 

Back to the original C++11 answer, explaining why the requested syntax is not supported:

A technical reason is that C++ allows the definition and declaration to be separate.

template<class A, class B> auto sum(A a, B b) -> decltype(a + b);  template<class A, class B> auto sum(A a, B b) -> decltype(a + b) { } 

The definition of the template could be in the header. Or it could be in another file, so that you don't have to wade through pages and pages of function definitions when looking through an interface.

C++ has to account for all possibilities. Restricting trailing return types to just function definitions means that you can't do something as simple as this:

template<class A, class B> class Foo {   auto sum(A a, B b) -> decltype(a + b); }  template<class A, class B> auto Foo<A, B>::sum(A a, B b) -> decltype(a + b) { } 

A note from the future (C++14 and on): you still cannot have a declaration with auto return type, if the definition is not available when the compiler sees the call.

like image 58
4 revs, 3 users 48% Avatar answered Sep 24 '22 15:09

4 revs, 3 users 48%


but we can't use templates like that anyway.

First, trailing return types aren't purely a template thing. They work for all functions. Secondly, says who? This is perfectly legal code:

template<class A, class B> auto sum(A a, B b) -> decltype(a + b);  template<class A, class B> auto sum(A a, B b) -> decltype(a + b) { } 

The definition of the template could be in the header. Or it could be in another file, so that you don't have to wade through pages and pages of function definitions when looking through an interface.

C++ has to account for all possibilities. Restricting trailing return types to just function definitions means that you can't do something as simple as this:

template<class A, class B> class Foo {   auto sum(A a, B b) -> decltype(a + b); }  template<class A, class B> auto Foo<A, B>::sum(A a, B b) -> decltype(a + b) { } 

And this is fairly common for many programmers. There's nothing wrong with wanting to code this way.

The only reason lambdas get away without the return type is because they have to have a function body defined with the definition. If you restricted trailing return types to only those functions where the definition was available, you wouldn't be able to use either of the above cases.

like image 44
Nicol Bolas Avatar answered Sep 24 '22 15:09

Nicol Bolas