Advantage of Using Trailing Return Type in C++11 Functions

Advantage of using trailing return type in C++11 functions

In this example, they mean the exact same thing.

However, there are a few advantages to using the trailing return type form consistently (Phil Nash calls these "East End Functions", since the return type is on the east end).

  1. Using parameters. Obviously when using parameters to determine the return type, you must use a trailing return type.

     template <typename T>
    auto print(T const& t) -> decltype(std::cout << t) { return std::cout << t; }
  2. Name lookup. In a trailing return type, name lookup includes the class scope for member function definitions. This means you don't have to retype the class if you want to return a nested class:

     Type C::foo() { ... }         // error: don't know what Type is
    C::Type C::foo() { ... } // ok

    auto C::foo() -> Type { ... } // ok
  3. Likewise, for defining member functions where the class name for some reason must be disambiguated to be in the global namespace and the return type is a class:

     D ::C::foo() { ... }         // error, parsed as D::C::foo() { ... }

    auto ::C::foo() -> D { ... } // ok
  4. A more reasonable ordering of information. Let's say you want to write a function to_string that takes an int and returns an string. That's a pretty sensible way of phrasing that. Function name, parameters, return type. You wouldn't say you want to write a string-returning function named to_string that takes an int. That's an awkward order of information. The name is the most important, followed by the parameters, followed by the return type. The trailing-return-type form allows you to order these pieces of information better.

There are cases where trailing-return-type is mandatory, there are cases where it is helpful, and there are cases where it does the same thing. There are not cases where it is worse for reasons other than simply character count.

Plus, mathemtically we're used to thinking of functions as A -> B and not so much B(A), and so auto(*)(A) -> B as a function pointer taking an A and returning a B is a little closer to that view than B(*)(A).


On the other hand, writing auto main() -> int looks ridiculous.

But honestly, that's mostly because of unfamiliarity. There's nothing inherently ridiculous about it. If anything, it's a bit unfortunate that the language uses auto here as a way to declare a function - not because it's too long (i.e. some other languages use fun or even fn) - but because it's not really distinct from other uses of auto. If it were func instead, I think it would have been better (although it would not make any sense to change now).


Ultimately, this is purely opinion based. Just write code that works.

Should the trailing return type syntax style become the default for new C++11 programs?

There are certain cases where you must use a trailing return type. Most notably, a lambda return type, if specified, must be specified via a trailing return type. Also, if your return type utilizes a decltype that requires that the argument names are in scope, a trailing return type must be used (however, one can usually use declval<T> to work around this latter issue).

The trailing return type does have some other minor advantages. For example, consider a non-inline member function definition using the traditional function syntax:

struct my_awesome_type
{
typedef std::vector<int> integer_sequence;

integer_sequence get_integers() const;
};

my_awesome_type::integer_sequence my_awesome_type::get_integers() const
{
// ...
}

Member typedefs are not in scope until after the name of the class appears before ::get_integers, so we have to repeat the class qualification twice. If we use a trailing return type, we don't need to repeat the name of the type:

auto my_awesome_type::get_integers() const -> integer_sequence
{
// ...
}

In this example, it's not such a big deal, but if you have long class names or member functions of class templates that are not defined inline, then it can make a big difference in readability.

In his "Fresh Paint" session at C++Now 2012, Alisdair Meredith pointed out that if you use trailing return types consistently, the names of all of your functions line up neatly:

auto foo() -> int;
auto bar() -> really_long_typedef_name;

I've used trailing return types everywhere in CxxReflect, so if you're looking for an example of how code looks using them consistently, you can take a look there (e.g, the type class).

Trailing return type in non-template functions

In addition to sergeyrar's answer, those are the points I could imagine someone might like about trailing return types for non-template functions:

  1. Specifying a return type for lambda expressions and functions is identical w.r.t. the syntax.

  2. When you read a function signature left-to-right, the function parameters come first. This might make more sense, as any function will need the parameters to produce any outcome in the form of a return value.

  3. You can use function parameter types when specifying the return type (the inverse doesn't work):

    auto f(int x) -> decltype(x)
    {
    return x + 42;
    }

    That doesn't make sense in the example above, but think about long iterator type names.

Advantages of arrow syntax in function declaration

what are the advantages of using [trailing return type syntax] ?

A possible advantage: the trailing return type is SFINAE friendly.

Your getsum() function, with trailing return type (-> decltype( l + r )), is enabled only when exist an plus operator between l and r.

If you call it with a couple of arguments that doesn't support the sum (by example, a couple of std::vector's) you get a substitution failure that isn't an error (SFINAE).

So another getsum() function can be called.

The following is a full example that compile and where the "do something different" version is called from getsum(a, b)

#include <vector>

template <typename ... Ts>
auto getsum (Ts...)
{ /* do something different */ }

template <typename L, typename R>
auto getsum (L l, R r) -> decltype( l + r )
{ return l + r; }

int main ()
{
std::vector<int> a, b;

getsum(a, b);
}

But if you remove the trailing return type

template <typename L, typename R> 
auto getsum (L l, R r) // <<--- no more tailing return type!!!
{ return l + r; }

the code doesn't compile anymore, because you don't have a substitution failure but an hard error.

Trailing return types in member functions

That's because the access function you're trying to call still doesn't exist at the time you're trying to call it. (Live version.)

#include <string>
#include <iostream>

namespace Params {
struct t_param1 {};
struct t_param2 {};
};

template <class t_detail>
struct Select;
template <>
struct Select<Params::t_param1> {
using type = Params::t_param1;
};
template <>
struct Select<Params::t_param2> {
using type = Params::t_param2;
};

template <class t_object>
class Tester {
using t_uint32 = uint32_t;
using t_string = std::string;
t_uint32 m_param1;
t_string m_param2;
auto access(const Params::t_param1 &) -> decltype(m_param1);
auto access(const Params::t_param2 &) -> decltype(m_param2);
template <class t_entity>
void assign(const Params::t_param1 &, t_entity &&entity);
template <class t_entity>
void assign(const Params::t_param2 &, t_entity &&entity);
public:
template <class t_detail, class t_entity>
void assign(t_entity&& entity);
template <class t_detail>
auto access() -> decltype(access(typename Select<t_detail>::type()));
};

template <class t_object>
template <class t_detail, class t_entity>
void Tester<t_object>::assign(t_entity &&entity) {
assign(typename Select<t_detail>::type(), entity);
}

template <class t_object>
template <class t_entity>
void Tester<t_object>::assign(const Params::t_param1 &, t_entity &&entity) {
m_param1 = entity;
std::cout << "Assigned m_param2 with " << entity << std::endl;
}

template <class t_object>
template <class t_entity>
void Tester<t_object>::assign(const Params::t_param2 &, t_entity &&entity) {
m_param2 = entity;
std::cout << "Assigned m_param2 with " << entity << std::endl;
}

template <class t_object>
template <class t_detail>
auto Tester<t_object>::access()
-> decltype(access(typename Select<t_detail>::type())) {
return access(typename Select<t_detail>::type());
}

template <class t_object>
auto Tester<t_object>::access(const Params::t_param1 &)
-> decltype(m_param1) {
return m_param1;
}

template <class t_object>
auto Tester<t_object>::access(const Params::t_param2 &)
-> decltype(m_param2) {
return m_param2;
}

int main() {
auto tester = Tester<std::string>();
tester.assign<Params::t_param1>(32);
tester.assign<Params::t_param2>("something");

auto param1 = tester.access<Params::t_param1>();
auto param2 = tester.access<Params::t_param2>();

std::cout << "Access: param1 = " << param1 << std::endl;
std::cout << "Access: param2 = " << param2 << std::endl;
}

Could you stop writing in C++ as if it was Java, please? :)

Trailing return type usage when using CRTP

dclas is an incomplete type when the base class is instantiated. You need to do two things to make this work:

  • Defer the checking of the type of func1<T1>() until the type is complete
  • Use the template keyword on the dependent expression so that the template definition is parsed correctly:

We can do this by adding a layer of indirection:

namespace detail {
template <class T, class Func1Arg>
struct func1_t {
using type = decltype(std::declval<T>().template func1<Func1Arg>());
};
};

Then you use this trait as the trailing return type:

template<typename T1>
auto func1() -> typename detail::func1_t<dclas,T1>::type
{
return(static_cast<dclas*>(this)->template func1<T1>());
}

Trailing return type in C++14

Consider...

auto f(int x)
{
if (x == 2)
return 3;
return 2.1;
}

...this has an ambiguous return type - int or double. An explicit return type - whether prefixed or trailing - can disambiguate it and casts the return argument to the return type.

Trailing return types specifically are also useful if you want to use decltype, sizeof etc on some arguments:

auto f(int x) -> decltype(g(x))
{
if (x == 2)
return g(x);
return 2;
}


Related Topics



Leave a reply



Submit