为什么我们必须有迟到的退货类型?

时间:2011-05-05 09:32:24

标签: c++ decltype

看看这个:

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
}

在这样的例子后,必须出现问题(顺便说一下整个例子编译并按预期工作),无论如何这里是问题:

我们真的必须使用晚期返回类型的哈巴狗丑陋语法吗?正如我在这个例子中证明的那样,它可以“正常的方式”完成。

编辑(现在没有这个可怕的静态假人 - 所有人都在唱着所有的舞蹈)

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
}

6 个答案:

答案 0 :(得分:3)

有时它在没有尾随返回类型的情况下不起作用,因为编译器无法知道程序员要求它做什么或涉及的类型是什么。

采用这个简单的转发包装模板函数(这不是一个简单的例子,而是取自我不久前写的一些真实代码):

template<typename T, typename... A> auto fwd(T fp, A...a) -> decltype(fp(a...))
{
    // some other code
    return fp(a...);
};

可以使用任何类型的未知返回类型的未知函数指针调用此函数。它会工作,它将返回正确的类型,并且不会混淆编译器。

如果没有尾随返回类型,编译器将无法确定发生了什么。

您可以使用#define获得类似的效果并滥用逗号运算符,但只需付出代价即可。

答案 1 :(得分:3)

所有这些都缺少它添加的更好的可读性,而无需使用typedef或别名模板

auto f() -> void(*)();

将其与等效的

进行比较
void (*f())();

您还可以在指定的返回类型中访问this,但不能在早期返回类型中访问

class A {
  std::vector<int> a;
public:
  auto getVector() -> decltype((a));
  auto getVector() const -> decltype((a));
};

如果您反过来使用它,它将无效,因为this不在范围内,decltype((a))两种时间都具有相同的类型(没有隐式{{1} }会被添加,因此this的类型不会影响this的类型。

答案 2 :(得分:2)

可以在没有decltype的情况下完成,但这有一些缺点。您需要一个额外的模板参数,或者您需要依赖于添加两个类型为T的项目的约定,以生成类型为T的项目。

答案 3 :(得分:1)

如果我理解正确,你的意思是“迟到类型” C ++ 11调用尾随返回类型的内容。在你的情况下 目前,没有问题,如果你不想使用 尾随返回类型,没有理由这样做。如果退货 type取决于参数类型,但是,它可以是非常的 详细说明必须在decltype

中重复这些内容
template <typename T1, typename T2>
auto add( T1 lhs, T2 rhs ) -> decltype( lhs + rhs );

要避免使用尾随返回类型,您必须编写 类似的东西:

template <typename T1, typename T2>
decltype( (*(T1*)0 + *(T2*)0 ) add( T1 lhs, T2 rhs );

第一个例子中的返回类型更加清晰, 如果参数类型更复杂(例如某些东西 比如std::vector<T1>),它也更简洁:

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 );

答案 4 :(得分:1)

我没有得到这个问题,你问为什么语言允许你这样做:

template <typename T, typename U>
auto foo( X<T> lhs, X<U> rhs ) -> decltype( lhs + rhs );

而不是强制人工将{x}}模板添加到静态虚拟成员,以便您输入:

X

真的?我的意思是,如果你愿意,可以使用其他不那么繁琐的结构,例如:

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 );

然后在需要时被迫使用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 ); 的特定变体:

lvalue_of

然后为template <typename T, typename U> decltype( lvalue_of< X<T> > + lvalue_of< X<U> const > ) foo( X<T> lhs, X<U> const & rhs ); 创建额外的变体...对于你可能会遇到的每个潜在用例,但为什么你更喜欢标准强​​迫你去那些奇怪的错误(你会记得吗?)更新rvalue_ref如果更改参数?)构造来定义类型,当参数声明后,编译器可以轻松地以安全简单的方式自行完成它?

通过这种推理,你也应该完全放弃语言中的lambda,它们根本不是一个启用功能。启用功能是能够在模板中使用本地类:

decltype

可以很容易地实现为:

std::function< void () > f = [](){ std::cout << "Hi"; };

然后你可能会想到很多其他功能可以从语言中轻易删除,因为有解决方法。

答案 5 :(得分:0)

以下是有用的延迟返回类型的示例。

#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