Take a look at this:
template<class T>
struct X
{
private:
T value_;
public:
X():value_(T()) {}
X(T value):value_(value) {}
T getValue() const
{
return value_;
}
static const T value 开发者_开发百科= 0; //this is dummy
template<class D, class U>
friend decltype(X<D>::value + X<U>::value) operator + (X<D> lhs, X<U> rhs);
};
template<class T, class U>
decltype(X<T>::value + X<U>::value) operator + (X<T> lhs, X<U> rhs)
{
return lhs.getValue() + rhs.getValue();
/* THIS COMPILES WITH VS2010 but line below doesn't which is incorrect
* behaviour in my opinion because: friendship has been declared and
* access to private data should be granted.*/
return lhs.value_ + rhs.value_; //THIS COMPILES WITH GCC 4.6
}
And after example like this there must come the question (by the way the whole example compiles and works as intended), anyway here is the question:
Do we really have to have the pug ugly syntax with late return type? As I proved in this example it can be done "the normal way".
Edited (now without this dreaded static dummy - all singing all dancing)
template<class T>
struct X
{
private:
T value_;
public:
typedef T value_type;
X():value_(T()) {}
X(T value):value_(value) {}
T getValue()const { return value_; }
template<class D, class U>
friend X<decltype(typename X<D>::value_type() + typename X<U>::value_type())> operator + (X<D> lhs, X<U> rhs);
};
template<class T, class U>
X<decltype(typename X<T>::value_type() + typename X<U>::value_type())> operator + (X<T> lhs, X<U> rhs)
{
return lhs.getValue() + rhs.getValue();
//return lhs.value_ + rhs.value_; //VS is __wrong__ not allowing this code to compile
}
Sometimes it just doesn't work without trailing return type, because the compiler has no way of knowing what the programmer asks it to do or what the involved types are.
Take this simple forwarding-wrapper template function (that's not a made up example, but taken from some real code I wrote not long ago):
template<typename T, typename... A> auto fwd(T fp, A...a) -> decltype(fp(a...))
{
// some other code
return fp(a...);
};
This function can be called with any kind of unknown function pointer that has any kind of unknown return type. It will work, it will return the correct type, and it doesn't confuse the compiler.
Without the trailing return type, the compiler would have no way of figuring out what's going on.
You could get a similar effect with a #define
and abusing the comma operator, but eugh... at what price.
All this is missing the better readability it adds, without needing to use typedefs or alias templates
auto f() -> void(*)();
Compare that to the equivalent
void (*f())();
You can also access this
in a late specified return type, but not in the early return type
class A {
std::vector<int> a;
public:
auto getVector() -> decltype((a));
auto getVector() const -> decltype((a));
};
If you had it the other way around, it wouldn't work, because this
would not be in scope, and decltype((a))
would have the same type both time (no implicit this
would be added, so the type of this
could not influence the type of (a)
).
It can be done without decltype
, but this has some drawbacks. You either need an additional template parameter or you need to rely on the convention that adding two items of type T
produces an item of type T
.
If I understand you correctly, by "late return type", you mean
what C++11 calls the trailing return type. In the case you
present, there's no problem, and if you don't want to use the
trailing return type, there's no reason to do so. If the return
type depends on the argument types, however, it can be extremely
verbose to have to repeat them in the decltype
:
template <typename T1, typename T2>
auto add( T1 lhs, T2 rhs ) -> decltype( lhs + rhs );
To avoid using the trailing return type, you'd have to write something like:
template <typename T1, typename T2>
decltype( (*(T1*)0 + *(T2*)0 ) add( T1 lhs, T2 rhs );
It's a lot clearer what the return type is in the first example,
and if the parameter types are more complicated (e.g. something
like std::vector<T1>
), it's also a lot more succinct:
template <typename T>
auto findInVector( std::vector<T> const& v ) -> decltype( v.begin() );
vs.
template <typename T>
typename std::vector<T>::const_iterator
findInVector( std::vector<T> const& v );
I don't get this question, are you asking why the language allows you to do:
template <typename T, typename U>
auto foo( X<T> lhs, X<U> rhs ) -> decltype( lhs + rhs );
Instead of forcing you to artificially add a static dummy member to the X
template so that you can type:
template <typename T, typename U>
decltype( X<T>::unneeded_artificial_static_member +
X<U>::unneeded_artificial_static_member )
foo( X<T> lhs, X<U> rhs ) -> decltype( lhs + rhs );
Seriously? I mean, you can use other less burdensome constructs if you want, like:
template <typename T>
T& lvalue_of();
template <typename T, typename U>
decltype( lvalue_of< X<T> > + lvalue_of< X<U> > )
foo( X<T> lhs, X<U> rhs );
And then be forced to use specific variants of the lvalue_of
when needed:
template <typename T, typename U>
decltype( lvalue_of< X<T> > + lvalue_of< X<U> const > )
foo( X<T> lhs, X<U> const & rhs );
And then create extra variants for rvalue_ref
... for each potential use case that you might come around, but why on earth would you prefer the standard to force you to those weird error prone (will you remember to update the decltype
if you change the argument?) constructs to define the type, when after the argument declaration the compiler can so easily do it itself in a safe simple safe way?
With that line of reasoning, you should also drop lambdas from the language altogether, they are not an enabling feature at all. The enabling feature was being able to use a local class in a template:
std::function< void () > f = [](){ std::cout << "Hi"; };
can be easily implemented as:
struct lambda {
void operator()() {
std::cout << "Hi";
}
};
std::function< void () > f = lambda();
And then you can probably think of a good amount of other features that can be as easily dropped from the language, since there are workarounds.
Here is an example of useful late return type.
#include <iostream>
#include <string>
using namespace std;
template <class T1>
auto inchesToFeet(int inch) -> T1
{
T1 inches = static_cast<T1>(inch);
T1 value = inches/12;
return value;
};
int _tmain(int argc, _TCHAR* argv[])
{
int inches = 29;
int approxWood = inchesToFeet<int>(inches);
cout << approxWood << " feet of wood" << endl;
float exactWire = inchesToFeet<float>(inches);
cout << exactWire << " feet of wire" << endl;
return 0;
}
Output:
2 feet of wood
2.41667 feet of wire
精彩评论