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:
Specifying a return type for lambda expressions and functions is identical w.r.t. the syntax.
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.
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
Is Multiplication and Division Using Shift Operators in C Actually Faster
How to Easily Format My Data Table in C++
Why Don't C++ Compilers Define Operator== and Operator!=
Why Does C++ Output Negative Numbers When Using Modulo
C++ Preprocessor #Define-Ing a Keyword. Is It Standards Conforming
Resolving Ambiguous Overload on Function Pointer and Std::Function For a Lambda Using + (Unary Plus)
Using Unicode in C++ Source Code
Behavior of Post Increment in Cout
Access Friend Function Defined in Class
Should I Compile With /Md or /Mt
How Is Std::String Implemented
Why Can Lambdas Be Better Optimized by the Compiler Than Plain Functions
Why Is the Destructor of a Future Returned from 'Std::Async' Blocking
Why Do I See Strange Values When I Print Uninitialized Variables
How to Read and Manipulate CSV File Data in C++