Why can't we have automatically deduced return types?

C++C++11

C++ Problem Overview


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?

C++ Solutions


Solution 1 - C++

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.

Solution 2 - C++

> 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.

Solution 3 - C++

There is no technical reason why it is not possible. The main reason they haven't is because the C++ language moves very slowly and it takes a very long time for features to be added.

You can nearly get the nice syntax you want with lambdas (but you can't have templacised arguments in lambdas, again for no good reason).

auto foo = [](int a, double b)
{
    return a + b;
};

Yes, there are some cases where the return type could not be automatically deduced. Just like in a lambda, it could simply be a requirement to declare the return type yourself in those ambiguous cases.

At the moment, the restrictions are just so arbitrary as to be quite frustrating. Also see the removal of concepts for added frustration.

Solution 4 - C++

In a blog post from Dave Abrahams, he discusses a proposal for having this function syntax:

[]min(x, y)
{ return x < y ? x : y }

This is based off of possible proposal for polymorphic lambdas. He has also started work here on updating clang to support this syntax.

Solution 5 - C++

Little hope to persuade the committee to add a language feature like this, though there is no technical obstacles. But maybe it's possible to persuade the GCC group to add a compiler extension.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBrendan LongView Question on Stackoverflow
Solution 1 - C++user405725View Answer on Stackoverflow
Solution 2 - C++Nicol BolasView Answer on Stackoverflow
Solution 3 - C++AyjayView Answer on Stackoverflow
Solution 4 - C++Paul Fultz IIView Answer on Stackoverflow
Solution 5 - C++Peng WangView Answer on Stackoverflow