参数类型推导,引用和rvalues

时间:2011-01-02 13:23:12

标签: c++ templates reference type-inference rvalue

考虑一下函数模板需要转发参数的情况,同时保持它的左值,以防它是非常量左值,但它本身与参数实际上是无关的,如:

template <typename T>
void target(T&) {
    cout << "non-const lvalue";
}

template <typename T>
void target(const T&) {
    cout << "const lvalue or rvalue";
}


template <typename T>
void forward(T& x) {
    target(x);
}

如果x是左值,而不是T被推导为常量类型,则会出错:

int x = 0;
const int y = 0;

forward(x); // T = int
forward(y); // T = const int
forward(0); // Hopefully, T = const int, but actually an error
forward<const int>(0); // Works, T = const int

似乎forward处理rvalues(不调用显式模板参数)需要forward(const T&)重载,即使它的正文是完全重复的。

有没有办法避免这种重复?

4 个答案:

答案 0 :(得分:5)

这是一个已知问题,也是C ++ 0x中rvalue引用的目的。这个问题在C ++ 03中没有通用的解决方案。

有一些古老的历史原因,为什么会发生这种情况,这是非常无意义的。我记得曾经问过一次,答案让我非常沮丧。

答案 1 :(得分:2)

  

x是右值时

x永远不是右值,因为名字是左值。

  

有没有办法避免这种重复?

C ++ 0x中有一种方法:

#include <utility>

template <typename T>
void forward(T&& x)   // note the two ampersands
{
    target(std::forward<T>(x));
}

由于引用了折叠规则,表达式std::forward<T>(x)与您自己的转发函数的参数具有相同的值类别。

答案 2 :(得分:2)

一般来说,模板应该的这个讨厌的问题需要重复,因为变量是const或不是或者引用的语义是完全不同的。

C ++ 11对此的解决方案是“decltype”,但这是一个坏主意,因为它所做的只是复合和已经破坏的类型系统。

无论标准或委员会说什么,“const int”都不是,也永远不会是一种类型。 “int&amp;”也不会永远是一个类型。因此,永远不允许模板中的类型参数绑定到这样的非类型,并且幸运的是扣除这种情况。不幸的是,您仍然可以明确强制进行无原则的替换。

有一些愚蠢的规则试图“修复”这个问题,例如“const const int”减少到“const int”,我甚至不确定如果你得到“int&amp;&amp;”会发生什么:记住即使标准也不计算“int&amp;”作为一种类型,有一个“int lvalue”类型,但它是不同的:

int x;       // type is lvalue int
int &y = x;  // type is lvalue int

解决这个问题的正确方法实际上很简单:一切都是可变对象。抛出“const”(它没那么有用)并抛弃引用,左值和右值。很明显,所有类类型都是可寻址的,rvalue或不是(“this”指针是地址)。委员会徒劳地试图禁止分配和解决rvalues ..寻址案件有效但很容易通过琐碎的演员逃脱。赋值情况根本不起作用(因为赋值是一个成员函数,而rvalues是非const的,你总是可以赋值给一个类型的rvalue)。

无论如何模板元编程人群都有“decltype”,你可以找到包含任何“const”和“&amp;”的声明的编码。比特然后你可以使用各种库运算符分解该编码。之前无法做到这一点,因为该信息实际上不是类型信息(“ref”实际上是存储分配信息)。

答案 3 :(得分:0)

假设有k个参数,据我所知,C ++ 03中唯一的“解决方案”是手动写出2 ^ k个转发函数,并采用&const&个参数的每种可能组合。为了说明,假设target()实际上有2个参数。然后你需要:

template <typename T>
void forward2(T& x, T& y) {
    target(x, y);
}

template <typename T>
void forward2(T& x, T const& y) {
    target(x, y);
}

template <typename T>
void forward2(T const& x, T& y) {
    target(x, y);
}

template <typename T>
void forward2(T const& x, T const& y) {
    target(x, y);
}

显然这对于​​大k来说非常笨重,因此其他答案提到的C ++ 0x中的右值引用。