2011-12-01 50 views
18

为什么下面的代码有效:为什么完美的转发功能必须被模板化?

template<typename T1> 
void foo(T1 &&arg) { bar(std::forward<T1>(arg)); } 

std::string str = "Hello World"; 
foo(str); // Valid even though str is an lvalue 
foo(std::string("Hello World")); // Valid because literal is rvalue 

但不是:

void foo(std::string &&arg) { bar(std::forward<std::string>(arg)); } 

std::string str = "Hello World"; 
foo(str); // Invalid, str is not convertible to an rvalue 
foo(std::string("Hello World")); // Valid 

为什么不例子2中,左值获得同样的方式,它并实例1中得到解决?

此外,为什么标准认为需要在std :: forward中提供参数类型而不是简单地推导它呢?无论何种类型,简单地向前转就显示意图。

如果这不是一个标准的东西,只是我的编译器,我使用msvc10,这将解释蹩脚的C++ 11支持。

感谢

编辑1:更改文字的 “Hello World” 进行的std :: string( “Hello World” 的),使右值。

+0

酒吧会发生什么?编译并不意味着它是必须的。我相信它应该分别为'void foo(T1&arg)'和'void foo(std :: string&arg)'。 – AJG85

+1

“Hello World”不是一个右值,它是一个类型为const char [12]的左值。 – GManNickG

+0

@ AJG85酒吧里发生的事情并不重要。 &&表示右值引用。 – Mranz

回答

15

首先,read this得到转发的完整想法。 (是的,我正在将这个答案的大部分委托给其他地方。)

总之,转发意味着左值保持左值和右值不变。你不能用一种类型来完成,所以你需要两个。因此,对于每个转发的参数,您需要该参数的两个版本,这需要函数的总数组合。你可以用代码编写该函数的所有组合,但是如果你使用模板,那么根据需要为你生成各种组合。


如果你想优化复制和移动,如:

struct foo 
{ 
    foo(const T& pX, const U& pY, const V& pZ) : 
    x(pX), 
    y(pY), 
    z(pZ) 
    {} 

    foo(T&& pX, const U& pY, const V& pZ) : 
    x(std::move(pX)), 
    y(pY), 
    z(pZ) 
    {} 

    // etc.? :(

    T x; 
    U y; 
    V z; 
}; 

那么你应该停下来做这样说:

struct foo 
{ 
    // these are either copy-constructed or move-constructed, 
    // but after that they're all yours to move to wherever 
    // (that is, either: copy->move, or move->move) 
    foo(T pX, U pY, V pZ) : 
    x(std::move(pX)), 
    y(std::move(pY)), 
    z(std::move(pZ)) 
    {} 

    T x; 
    U y; 
    V z; 
}; 

你只需要一个构造函数。 准则:如果您需要您自己的数据副本,请在参数列表中进行复制;这可以决定复制或移动到调用者和编译器。

+1

所以第一个例子工作的原因是因为T1实际上被解析为等同于'void foo(const std :: string&&arg)',它被使用引用减少到void foo(const std :: string&arg)扣除规则?它在示例2中失败,因为该字符串没有左值过载?是否有任何定义模板函数的最佳实践,以至于它至少应该是相对明显的类型?理想情况下,我正在寻找一种很好的方法来避免2^N重载,并明确使用该类型。 – Mranz

+1

@Mranz:正确。那么,你要做什么?转发和模板是为了用于转发,你不应该真的需要知道类型。 – GManNickG

+0

可以说我有一个构造函数,它需要像'Class(.. some args ...,vector items)这样的矢量'。理想情况下,我想允许这个参数避免使用移动语义或作为右值的副本。为了支持这一点,我要么必须模板项目,要么创建右值超载。如果我将其模板化,实际的类型将在定义中丢失,实际的参数将由我的课程的消费者通过阅读头文件或一些评论推演出来。 – Mranz