“不一样”是您在编写可完美转换的构造函数时使用的模式——当传递的类型是您自己类型的某个变体时,您不想使用此转换器。奇怪的是它被copy-pasta包含在这里。
真的,您想使用“可以将其分配给字符串”的特征:std::enable_if_t<std::is_assignable<std::string, String>::value>>,因为这是您所关心的。你可以更进一步,测试它是否可分配(如果是,使用它),如果它是可转换的(如果是,转换,然后分配),但我不会。
简而言之,条件看起来像是来自相关测试的复制粘贴。你真的不想限制太多。
至于为什么它击败了选项#2,如果你的容器中的std::string 已经分配了内存,它可以从char const* 复制而不分配更多。如果取而代之的是string&&,则char const* 首先转换为string,然后是移动分配的。我们有两个字符串,其中一个被丢弃。
您所看到的是内存分配开销。
完美转发不必分配内存。
现在,为了完整起见,还有另一种选择。实施起来有点疯狂,但它几乎与选项 #4 一样有效,并且几乎没有缺点。
选项 5:键入擦除分配。 assignment_view<std::string>.
编写一个类型擦除“分配给类型T”的类。把它作为你的论据。在里面使用。
这比完美转发更易教。该方法可以是虚拟的,因为我们采用的是具体类型(分配给字符串的具体类型)。类型擦除发生在分配器的构造过程中。为分配自的每种类型生成一些代码,但代码仅限于分配,而不是整个函数体。
每次分配都有一些开销(类似于虚函数调用,主要是由于指令缓存未命中造成的)。所以这并不完美。
您调用a.assign_to(name) 代替name = a 来完成分配以获得最大效率。如果您喜欢语法,可以使用name << std::move(a);。
为了获得最大效率,分配擦除视图(无论您想怎么称呼它)只能用于生成一个分配:这允许它优化移动语义。您还可以制作一个智能的,在基于 && 和 & 的 assign-from 上执行不同的操作,但需要额外的函数指针开销。
here 我键入删除T == ? 的概念。这只需要类型擦除T = ? 的概念。 (我现在可以使用 Ts&&... ctor 对类型擦除对象进行 {} 初始化的语法更好一点:这是我第一次尝试。)
live example 类型擦除到分配给std::string。
template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;
template<class T>struct tag{using type=T;};
template<class...>struct types{using type=types;};
template<class T>
using block_deduction = typename tag<T>::type;
template<class F, class Sig, class T=void>
struct erase_view_op;
template<class F, class R, class...Ts, class T>
struct erase_view_op<F, R(Ts...), T>
{
using fptr = R(*)(void const*, Ts&&...);
fptr f;
void const* ptr;
private:
template<class U>
erase_view_op(U&& u, int):
f([](void const* p, Ts&&...ts)->R{
U& u = reinterpret_cast<U&>( *static_cast<std::decay_t<U>*>(const_cast<void*>(p)) );
return F{}( u, std::forward<Ts>(ts)... );
}),
ptr( static_cast<void const*>(std::addressof(u)) )
{}
public:
template<class U, class=std::enable_if_t< !std::is_same<std::decay_t<U>,erase_view_op>{} && (std::is_same<void,R>{} || std::is_convertible< std::result_of_t<F(U,Ts...)>, R >{}) >>
erase_view_op(U&& u):erase_view_op( std::forward<U>(u), 0 ){}
template<class U=T, class=std::enable_if_t< !std::is_same<U, void>{} >>
erase_view_op( block_deduction<U>&& u ):erase_view_op( std::move(u), 0 ){}
erase_view_op( erase_view_op const& ) = default;
erase_view_op( erase_view_op&& ) = default;
R operator()( Ts... ts ) const {
return f( ptr, std::forward<Ts>(ts)... );
}
};
struct assign_lhs_to_rhs {
template<class lhs, class rhs>
void operator()(lhs&& l, rhs& r)const {
r = std::forward<lhs>(l);
}
};
template<class T>
using erase_assignment_to = erase_view_op< assign_lhs_to_rhs, void(T&), T >;
using string_assign_to = erase_assignment_to< std::string >;
如前所述,它与擦除到== 的类型非常相似。我做了一些适度的改进(void 返回类型)。一个完美的转发(到T{})ctor 会比block_deduction<U>&& 更好(当你得到{} 而不是{{}} 构造)。