Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Omit return type in C++11

I've recently found myself using the following macro with gcc 4.5 in C++11 mode:

#define RETURN(x) -> decltype(x) { return x; }

And writing functions like this:

template <class T>
auto f(T&& x) RETURN (( g(h(std::forward<T>(x))) ))

I've been doing this to avoid the inconvenience having to effectively write the function body twice, and having keep changes in the body and the return type in sync (which in my opinion is a disaster waiting to happen).

The problem is that this technique only works on one line functions. So when I have something like this (convoluted example):

template <class T>
auto f(T&& x) -> ...
{
   auto y1 = f(x);
   auto y2 = h(y1, g1(x));
   auto y3 = h(y1, g2(x));
   if (y1) { ++y3; }
   return h2(y2, y3);
}

Then I have to put something horrible in the return type.

Furthermore, whenever I update the function, I'll need to change the return type, and if I don't change it correctly, I'll get a compile error if I'm lucky, or a runtime bug in the worse case. Having to copy and paste changes to two locations and keep them in sync I feel is not good practice.

And I can't think of a situation where I'd want an implicit cast on return instead of an explicit cast.

Surely there is a way to ask the compiler to deduce this information. What is the point of the compiler keeping it a secret? I thought C++11 was designed so such duplication would not be required.

like image 212
Clinton Avatar asked Dec 24 '10 01:12

Clinton


4 Answers

It would appear that g++ 4.8 is getting an implementation of auto return type deduction. The patch was put in by Jason Merrill who is also sending a paper for C++-1Y for the feature. The feature is available with -std=c++1y.

Still playing with it.

like image 56
emsr Avatar answered Sep 20 '22 09:09

emsr


The rationale for this behavior is given in the draft, 8.3.5p12:

A trailing-return-type is most useful for a type that would be more complicated to specify before the declarator-id:

template <class T, class U> auto add(T t, U u) -> decltype(t + u); 

rather than

template <class T, class U> decltype((*(T*)0) + (*(U*)0)) add(T t, U u); 

So this is really only meant to simplify the case where referring to the parameter names helps.

If you assume that C++ could always infer the return type of functions from the function body: this is not going to fly. It's a goal of C++ (and C) to allow modularity by separating declaration from implementation, so at the point of the call, you may not have the body of the function available. However, every caller needs to know the parameter types and the return type of every function/method being called.

like image 26
Martin v. Löwis Avatar answered Sep 19 '22 09:09

Martin v. Löwis


If you are simply trying to set the return type, make it a template argument. This way you can change everything related to the return type without actually changing the function. You can put a default return type if you want like in this example.

template <class R = int, class T>
R f(T&& x)
{
   ...
   return h2(y2, y3);
}

The code below demonstrates it's effectiveness.

DEMO CODE:

#include <iostream>
#include <iomanip>

template <class T, class S>
T h2(T& x, S& y)
{
  return x + y;
}

template <class R = int, class T>
R f(T& x)
{
  auto y2 = x;
  auto y3 = x;
  return h2(y2, y3);
}

int main(int argc, char** argv)
{
  int x = 7;
  std::string str = "test! ";

  auto d = f<double>(x);
  auto i = f(x); // use default type (int)
  auto s = f<std::string>(str);

  std::cout << std::fixed << std::setprecision(4);
  std::cout << "double: " << d << std::endl;
  std::cout << "int: " << i << std::endl;
  std::cout << "string: " << s << std::endl;

  return 0;
}

OUTPUT:

double: 14.0000
int: 14
string: test! test!

Unfortunately, the exact functionality you are looking for does not exist (yet) and is not part of the C++0x spec. However, it is possible this may be part of the C++1x spec when it is drafted. until then, stick to templates.

like image 20
Gravis Avatar answered Sep 19 '22 09:09

Gravis


EDIT: oops, I just realized that there's a scoping difference between the trailing-return-type specifier and the return statement. Specifically:

auto f(int a)
{
    char r[sizeof(f(a))+1];
    return r;
}

Kaboom!


Previous answer:

It's unfortunate that the language does not provide a syntax to have the compiler infer the return type in this case, because it's trivial to show that inference is possible.

Specifically, we are talking about the case where there is exactly one return statement inside the function.

Independent of where in the function that return statement is, or how complex the preceding code is, it should be clear that the following transformation is possible:

return (ugly expression);

into

auto return_value = (ugly expression);
return return_value;

If the compiler can infer the type of return_value (and according to the C++0x rules, it can), then the inferred type of return_value can be chosen as the return type of the function.

It therefore seems to me that a modification to C++0x where the trailing return type specifier should only be required when the multiplicity of return statements is not exactly one would be feasible and solve the problem.

like image 39
Ben Voigt Avatar answered Sep 23 '22 09:09

Ben Voigt