Trailing Return Type Using Decltype With a Variadic Template Function

trailing return type using decltype with a variadic template function

I think the problem is that the variadic function template is only considered declared after you specified its return type so that sum in decltype can never refer to the variadic function template itself. But I'm not sure whether this is a GCC bug or C++0x simply doesn't allow this. My guess is that C++0x doesn't allow a "recursive" call in the ->decltype(expr) part.

As a workaround we can avoid this "recursive" call in ->decltype(expr) with a custom traits class:

#include <iostream>
#include <type_traits>
using namespace std;

template<class T> typename std::add_rvalue_reference<T>::type val();

template<class T> struct id{typedef T type;};

template<class T, class... P> struct sum_type;
template<class T> struct sum_type<T> : id<T> {};
template<class T, class U, class... P> struct sum_type<T,U,P...>
: sum_type< decltype( val<const T&>() + val<const U&>() ), P... > {};

This way, we can replace decltype in your program with typename sum_type<T,P...>::type and it will compile.

Edit: Since this actually returns decltype((a+b)+c) instead of decltype(a+(b+c)) which would be closer to how you use addition, you could replace the last specialization with this:

template<class T, class U, class... P> struct sum_type<T,U,P...>
: id<decltype(
val<T>()
+ val<typename sum_type<U,P...>::type>()
)>{};

Can i use auto or decltype instead trailing return type?

auto& get_diag2(int(&ar)[3][3]){ // adding & auto because otherwise it converts the array to pointer
static int diag[3]{
ar[0][0], ar[1][1], ar[2][2]
};
return diag;
}

Will not work in a C++11 compiler. Using auto without a trailing return type was added to C++14 and acts like how auto works when using it for a variable. This means it will never return a reference type so you have to use auto& to return a reference to the thing you want to return.

If you do not know if you should return a reference or a value (this happens a lot in generic programming) then you can use decltyp(auto) as the return type. For example

template<class F, class... Args>
decltype(auto) Example(F func, Args&&... args)
{
return func(std::forward<Args>(args)...);
}

will return by value if func returns by value and return by reference if func returns a reference.


In short if you are using C++11 you have to specify the return type, either in front or as a trailing return type. In C++14 and above you can just use auto/decltype(auto) and let the compiler deal with it for you.

Reason for decltype in trailing return type

Note

Without it, the compiler would not be able to derive the return type of the template function.

This was later fixed in C++14 and the compiler doesn't need the trailing return type as it can infer the return type from the returned expression. The following works fine in C++14.

template <typename lhsT, typename rhsT>
auto add(const lhsT& lhs, const rhsT& rhs) {
return lhs + rhs;
}

But why is the syntax the way it is?

The use of trailing return type is useful when the return type is deduced from the arguments, which always are declared after the return type (one can't refer to something that hasn't been declared yet).

Also, a trailing return type does appear to be more natural in the sense that functional and mathematical notation uses this kind of declaration. I think most types of notation outside of C-style declarations commonly use trailing return type. E.g.:

f : X → Y

In above mathematical notation, f is the function name, X the argument, and Y the returned value.

Just because the flock of pink sheep reject the gray sheep doesn't make it unnatural.

In your second example it is not possible for the compiler to infer the return types as of above reason, i.e., that the arguments have not yet been declared. However, inference is possible if using the template parameters and std::declval, e.g.:

template <typename T, typename U>
decltype(std::declval<T>() + std::declval<U>()) add(const T& lhs, const U& rhs) {
return lhs + rhs;
}

std::declval acts as a lazy instantiation of a type and never evaluates, e.g., calls the constructor etc. It is mostly used when inferring types.

c++ 11 Trailing return type with decltype does not work as expected

It doesn't matter what the condition with the ?: operator is. The result type is calculated as a common type of the second and third operand. Here is part of how the common type and the value category of the ?: operator are calculated, see cppreference.com for the full details:

If the second and third operand are lvalues of the same type, then the result type will be a lvalue of that type.

If the types are unrelated lvalues, there are some more complex rules to determine the common type, but the result will be a prvalue, not a lvalue. In particular if the two types are arithmetic types such as double and long, then the usual arithmetic conversions are applied to obtain a common type. In the case of long and double that common type would be double. This is the same type calculation that would be done if you e.g. tried to add two different arithmetic type with +, hence the name usual arithmetic conversions.

Therefore decltype(a<b ? a:b) will be a reference type if a and b have the same type and otherwise it will not be a reference type.

This is why the function compiles. The common type is always such that both input types can be converted to it. It is also why the function has undefined behavior if the types are equal, because then decltype gives a reference and so you are going to return a reference to one of the function parameters.

decltype(a<b ? a:a) doesn't work with different types, because the common type of a and a is, as described above, a reference of the type of a. If b then has a different unrelated type, the result of a > b ? a : b will be a prvalue which cannot be bound to a lvalue reference.

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.

Variadic template argument list with changing return type

The straightforward

if constexpr (sizeof...(Args) == 1)
{
return AddComp<Args...>(entityID);
}

appears to work just fine. Clang demo, MSVC demo


Here's a solution that works with C++14:

template <typename T>
decltype(auto) AddComponent(EntityId entityID)
{
return AddComp<T>(entityID);
}

template <typename... Args>
decltype(auto) AddComponent(
std::enable_if_t<(sizeof...(Args) > 1), EntityId> entityID)
{
return std::tuple<decltype(AddComponent<Args>({}))...>{
AddComponent<Args>(entityID)...};
}

Demo

Trailing return type deduced from input arguments concrete use-case

I mean, we always know the return type of a function when we write it

Do we? So If you write this function template:

template<typename A, typename B>
/* ret */ foo(A a, B b) {
return a + b;
}

You can say for sure what ret is? If given two integer then it's an integer, sure. But if provided an integer and a long, it should be long due to promotions. And if one argument is a double, than the result should be a double two.

And what if that's two objects of some class types? Now we are calling an overloaded operator+, and there's absolutely no guessing what it may return.

I hope your'e convinced now that by saying we accept any two types, we cannot always be sure what is the type of an expression involving those types.

So a mechanism was added to the language to tell. Granted, this example is overly simple and is likely superseded by auto return types, but the general principle remains. When writing generic code, we often deal with unknown types. There is almost no knowing what should be the type of an expression involving them, or even if an expression like that is valid before the function is instantiated. decltype tells us that.



Related Topics



Leave a reply



Submit