六君子:特殊成员函数模板(Special Member Functions)

部分内容摘自: Universal References and the Copy Constructor – Eric Niebler

函数重载时,左值和const左值,编译器更喜欢谁

函数重载的时候,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);

避免对万能引用使用重载(Universal References)

看下面,你可能希望所有的左值都走第一个,其余的都走第二个。

但实际上,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() );
}

Special Member Functions

首先,为了方便记忆,我们先把那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里的value是std::string类型,这时就相当于把一个wrapper类型赋值给一个string类型的变量,自然就出错了:

严重性    代码    说明    项目    文件    行    禁止显示状态
错误    C2664    “std::basic_string,std::allocator>::basic_string(const std::basic_string,std::allocator> &)”: 无法将参数 1 从“wrapper”转换为“std::initializer_list<_Elem>”       

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

你可能感兴趣的:(开发语言,c++)