我正在嘗試移植一些為 GCC (8.2) 撰寫的代碼,以供 Clang 編譯:
#include <tuple>
struct Q{};
using TUP = std::tuple<Q>;
template<typename Fn>
inline
void feh(Fn&, const std::tuple<>*)
{}
template<typename Fn, typename H>
inline
void feh(Fn& fn, const std::tuple<H>*)
{
fn(H{});
}
template<typename Fn, typename H, typename... R>
inline
void feh(Fn& fn, const std::tuple<H, R...>*)
{
fn(H{});
using Rest = const std::tuple<R...>*;
feh<Fn, R...>(fn, static_cast<Rest>(nullptr));
}
template<typename Tuple, typename Fn>
inline
void fe(Fn& fn, const Tuple * tpl = nullptr)
{
feh(fn, tpl);
}
int main()
{
auto r = [] (Q const&) {};
TUP tup;
fe<TUP>(r, &tup);
}
GCC 8.2(和 12.1)可以很好地編譯代碼。但是,Clang 11.0.0(和 14.0.0)抱怨從feto的呼叫在andfeh之間是模棱兩可的。void feh(Fn& fn, const std::tuple<H>*) [with Fn = (lambda at <source>:38:14), H = Q]void feh(Fn& fn, const std::tuple<H, R...>*) [with Fn = (lambda at <source>:38:14), H = Q, R = <>]
https://godbolt.org/z/5E9M6a5c6
哪個編譯器是對的?
如何撰寫此代碼以便兩個編譯器都接受它?
和 fold 運算式都if constexpr可以在 C 17 中作業,但這是許多專案包含的庫頭檔案,并非所有專案都是用 C 17 編譯的。我需要一個適用于 C 11 的解決方案。
uj5u.com熱心網友回復:
哪個編譯器是對的?
Clang 拒絕代碼是錯誤的,因為第一個多載候選feh(Fn& fn, const std::tuple<H>*)者應該優先于另一個候選者feh(Fn& fn, const std::tuple<H, R...>*),因為前者比后者更專業。
換句話說,沒有包的版本被認為更專業,因此如果它與呼叫匹配,則應該是首選。
這是因為,基本上(大致)一個函式模板被認為比另一個更專業,后者應該能夠接受前者可以接受的所有模板引數,但反之則不行。
現在,在您給定的示例中,多載feh(Fn& fn, const std::tuple<H, R...>*)可以接受(或使用)前者feh(Fn& fn, const std::tuple<H>*)可以接受的所有模板引數,但反之則不然。因此前者比后者更專業。有關此程序的更多技術細節,請參閱模板推導中的部分排序程序是什么或來自[temp.deduct.partial]/10,其中指出:
函式模板 F 至少與函式模板 G 一樣特化,如果對于用于確定排序的每一對型別,來自 F 的型別至少與來自 G 的型別一樣特化。
F比GifF至少一樣特化asG并且G至少不像F.
(強調我的)
uj5u.com熱心網友回復:
我不確定哪個編譯器是正確的,但是...clang 是正確的,因為這兩個函式都匹配得很好。
C 11 解決方案可能是只添加Rest部件必須包含至少一種型別的要求,并且只需添加R1. 這意味著您的其余代碼可以保持不變:
template<typename Fn, typename H, typename R1, typename... R>
inline
void feh(Fn& fn, const std::tuple<H, R1, R...>*)
{
fn(H{});
using Rest = const std::tuple<R1, R...>*;
feh<Fn, R1, R...>(fn, static_cast<Rest>(nullptr));
}
C 17 解決方案是洗掉其他feh多載并使用折疊運算式:
template <typename Fn, typename... H>
inline void feh(Fn& fn, const std::tuple<H...>*) {
(..., fn(H{}));
}
這是逗號運算子的一元左折疊,“展開”變為:
(((fn(H1{}), fn(H2{})), ...), fn(Hn{}))
uj5u.com熱心網友回復:
到目前為止,最簡單的解決方案是if constexpr:
template<typename Fn, typename H, typename... R>
inline
void feh(Fn& fn, const std::tuple<H, R...>*)
{
fn(H{});
if constexpr (sizeof...(R) > 0) {
using Rest = const std::tuple<R...>*;
feh<Fn, R...>(fn, static_cast<Rest>(nullptr));
}
}
并洗掉有問題的過載。
uj5u.com熱心網友回復:
如何撰寫此代碼以便兩個編譯器都接受它?
您無需遞回即可撰寫代碼
template<typename Fn, typename ... Ts>
void fe(Fn& fn, const std::tuple<Ts...>* = nullptr)
{
// Trick to simulate fold expression of c 17
const int dummy[] = {0, (static_cast<void>(fn(Ts{})), 0)...};
static_cast<void>(dummy); // Avoid warning about unused variable
}
在 C 17 中會變成
template<typename Fn, typename ... Ts>
void fe(Fn& fn, const std::tuple<Ts...>* = nullptr)
{
(static_cast<void>(fn(Ts{})), ...);
// static_cast is here to handle evil overloaded operator comma (for type returned by Fn)
// might be omitted if you know you are not in that pathological case
}
[演示](fe(r, &tup);)
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/495876.html
