部分内容摘自: Universal References and the Copy Constructor – Eric Niebler
函数重载的时候,const参数和非const参数,在编译器看来,是完全不同的东西,是可以重载的,
而且在左值的时候,非const的优先级更高。
下面的代码,编译器选择的是void foo2(int & t)
void foo2(int const & t)
{/*...*/
}
void foo2(int & t)
{/*...*/
}
int main()
{
int var = 1;
foo2(var);
return 1;
}
编译器选择:void foo2(int & t);
下面的,编译器自然选择的是:
void foo2(int const & t)
{/*...*/
}
void foo2(int & t)
{/*...*/
}
int main()
{
foo2(1);
return 1;
}
编译器选择:void foo2(int const & t);
看下面,你可能希望所有的左值都走第一个,其余的都走第二个。
但实际上,const的左值走第一个,其余都走第二个,也就是说普通的左值也走第二个。因为非const的时候,被实例化为T&,这个要比const T&优先级更高(对编译器更有吸引力?)
template
void foo( T const & t )
{/*...*/}
template
void foo( T && t )
{/*...*/}
有个解决方法:用万能引用来接住参数,然后内部通过判断是否为左值,进行重载函数的分发。
template
void foo_impl( T && t, std::true_type )
{/* LVALUES HERE */}
template
void foo_impl( T && t, std::false_type )
{/* RVALUES HERE */}
template
void foo( T && t )
{
foo_impl( std::forward(t),
std::is_lvalue_reference() );
}
首先,为了方便记忆,我们先把那6个特殊成员函数称为六君子。
看下面的例子,如果U是int,double,string之类的,那还行,模板生成了对应的普通构造函数。value被实例化成了对应的int,double和string。
template
struct wrapper
{
T value;
template
wrapper( U && u )
: value( std::forward(u) ) {}
};
// The array is perfectly forwarded to the
// string constructor.
wrapper str("hello world");
但是,如果U的类型就是wrapper,问题就出现了。
// Copy the wrapper
wrapper str2 = str;
这时由万能引用所在的模板实例化出了个copy constructor:
wrapper (wrapper&)
但是,wrapper的六君子中的copy constructor是带const的
应该类似这样吧:
wrapper (const wrapper&)
注意,这里插一句:
这里面有个复杂的过程,本来模板不能作为构造函数的,所以编译器自动生成了个copy constructor:
wrapper( wrapper const & that )
: value( that.value ) {}
但在生成之后,编译器紧接着进入了重载的逻辑流程,所以它还是把模板给实例化了。然后它和模板进行了对比,由于模板是万能引用,产生了非const的构造函数版本(注意,它应该属于构造函数的重载,而不是属于六君子:那六个特殊成员函数中的一个是带const的。右值版本是不带const的,但这里是左值。):
说白了,编译器选择了由模板生成的更优的重载构造函数。而这个函数是要直接把wrapper对象赋值给value,但我们需要的是把wrapper里的value赋值给拿出来
wrapper( wrapper& that )
: value( value ) {}
所以,你说选谁吧,那肯定是选模板生成的这个非const版本了。
那么,wrapper
严重性 代码 说明 项目 文件 行 禁止显示状态
错误 C2664 “std::basic_string
template
struct wrapper
{
T value;
template
wrapper( U && u )
: value( std::forward(u) ) {}
// THIS IS COMPILER-GENERATED:
wrapper( wrapper const & that )
: value( that.value ) {}
};
最理想肯定是走
wrapper( wrapper const & that )
: value( that.value ) {}
如果是这样就能成功,毕竟很明显,走的是const的copy constructor:
const wrapper str("hello world");
wrapper str2 = str;
改成这样:
// write this once and put it somewhere you can
// reuse it
template
using disable_if_same_or_derived =
typename std::enable_if::type>::value>::type;
template
struct wrapper
{
T value;
template>
wrapper(U && u)
: value(std::forward(u))
{}
};
int main()
{
// The array is perfectly forwarded to the
// string constructor.
wrapper str("hello world");
wrapper str2 = str;
return 1;
}