「不一樣」是在編寫完美轉換構造函數時使用的模式 - 當傳遞的類型是您自己類型的某種變體時,您不想使用此轉換器。可能是它包括在這裏的複製意大利麪。
真的,你想使用一個特性「這可以分配給一個字符串」: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);
。
爲了最大效率,分配刪除視圖(無論你怎麼稱呼它)只能用於生產一個任務:這使得它能夠優化移動語義。你也可以做一個聰明的人,在&&
和&
的基礎上做一些不同的事情,分配一個額外的函數指針開銷。
here I型抹去T == ?
概念。這隻需要類型擦除T = ?
的概念。 (我可以爲{}
初始化一點點用Ts&&...
男星到現在的類型擦除對象更好的語法:那是我的第一次嘗試)
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{}
)構造函數會比block_deduction<U>&&
一個更好的(當你{}
,而不是{{}}
建設)。
'std :: string'有一個賦值操作符,它需要'char const *'。如果LHS上的「std :: string」已經有足夠的空間,這個運算符不必分配內存。 – dyp
@dyp也適用於從std :: string複製。另外,#4不能接受字符串文字。 – bmanga
由於參數類型的原因,所有其他選項(但完美轉發)都要求調用者轉換爲'std :: string'。如果參數是一個「char const *」,即使生成的「std :: string」(函數參數)可以被複制,而沒有額外的分配給「name_」,也需要分配內存。至於選項#4,IIRC幻燈片上有一些錯誤。一個完美的轉發函數會使用'std :: is_convertible'。 –
dyp