2016-07-22 57 views
24

由於C++ 11,我們可使它可以接受的參數中的任何序列模板功能:C++參數包,受限於具有單一類型的實例?

template <typename... Ts> 
void func(Ts &&... ts) { 
    step_one(std::forward<Ts>(ts)...); 
    step_two(std::forward<Ts>(ts)...); 
} 

但是,假如它真的纔有意義,叫我在功能的情況下,每個參數具有相同的類型 - 儘管如此,任何數量的參數都可以。

這樣做的最好方法是什麼,即是否有一種很好的方法來限制模板在這種情況下生成一個很好的錯誤消息,或理想情況下,當參數不匹配時消除func參與重載解析?


我可以使它真正具體的,如果有幫助:

假設我有一些結構:

struct my_struct { 
    int foo; 
    double bar; 
    std::string baz; 
}; 

現在,我希望能夠做的事情一樣,打印的成員用於調試目的的結構體,對結構體進行序列化和反序列化,按順序訪問結構體的成員等等。我有一些代碼可以幫助解決這個問題:

template <typename V> 
void apply_visitor(V && v, my_struct & s) { 
    std::forward<V>(v)("foo", s.foo); 
    std::forward<V>(v)("bar", s.bar); 
    std::forward<V>(v)("baz", s.baz); 
} 

template <typename V> 
void apply_visitor(V && v, const my_struct & s) { 
    std::forward<V>(v)("foo", s.foo); 
    std::forward<V>(v)("bar", s.bar); 
    std::forward<V>(v)("baz", s.baz); 
} 

template <typename V> 
void apply_visitor(V && v, my_struct && s) { 
    std::forward<V>(v)("foo", std::move(s).foo); 
    std::forward<V>(v)("bar", std::move(s).bar); 
    std::forward<V>(v)("baz", std::move(s).baz); 
} 

(它看起來有點費力,產生這樣的代碼,但是我做了a small library前一段時間,以幫助這一點。)

所以,現在我想擴展它,以便它可以訪問的my_struct兩個實例與此同時。那就是,如果我想實現平等或比較操作的話。在boost::variant文件中,他們稱之爲「二元探視」,與「一元探視」形成對比。

也許,沒有人會希望做更多的二進制訪問。但是,假設我想要這樣做,一般n-ary訪問。然後,它看起來像這樣我猜

template <typename V, typename ... Ss> 
void apply_visitor(V && v, Ss && ... ss) { 
    std::forward<V>(v)("foo", (std::forward<Ss>(ss).foo)...); 
    std::forward<V>(v)("bar", (std::forward<Ss>(ss).bar)...); 
    std::forward<V>(v)("baz", (std::forward<Ss>(ss).baz)...); 
} 

但現在,它越來越多一點瘋狂的 - 如果有人經過一系列的甚至不是同一個結構類型的所有類型,代碼可以編譯仍並完成用戶完全意想不到的事情。

我想過做這樣的:

template <typename V, typename ... Ss> 
void apply_visitor(V && v, Ss && ... ss) { 
    auto foo_ptr = &my_struct::foo; 
    std::forward<V>(v)("foo", (std::forward<Ss>(ss).*foo_ptr)...); 
    auto bar_ptr = &my_struct::bar; 
    std::forward<V>(v)("bar", (std::forward<Ss>(ss).*bar_ptr)...); 
    auto baz_ptr = &my_struct::baz; 
    std::forward<V>(v)("baz", (std::forward<Ss>(ss).*baz_ptr)...); 
} 

,至少如果他們不匹配的類型使用它會導致編譯錯誤。但是,它也發生得太晚了 - 這發生在模板類型解決後,並且在我認爲重載解決之後。

我曾考慮過使用SFINAE,而不是返回void,使用std::enable_if_t並檢查參數包中每種類型的某種表達式std::is_same<std::remove_cv_t<std::remove_reference_t<...>>。但是對於一個,SFINAE表達式相當複雜,對於兩個,它也有一個缺點 - 假設有人有派生類struct my_other_struct : my_struct { ... },並且他們希望將它與訪客機制一起使用,所以一些參數是my_struct,有些是my_other_struct。理想情況下,系統會將所有引用轉換爲my_struct,並以這種方式應用訪問者,並且我使用成員指針foo_ptr,bar_ptr,baz_ptr的上例給出的示例會在那裏做正確的事情,但是我不清楚如何寫像SFINAE這樣的約束 - 我將不得不嘗試找到我猜測的所有參數的共同基礎?

有沒有一種很好的方法來調和這些問題?

+4

'std :: common_type'? – cpplearner

+0

你希望它們都是特定的已知類型,還是隻是爲了相同,未知類型? – Quentin

+0

@cpplearner:哦,那真是太好了,我不知道那個 –

回答

15

隨着std::common_type,這很簡單:

template <class... Args, class = std::common_type_t<Args...>> 
void foo(Args &&... args) { 

} 

這隻會是保證C++17SFINAE - 友好,雖然。 ClangGCC都已經這樣實現了。

+0

@JohanLundberg我已經把它放在那裏來模擬參數傳遞,但經過一些測試後,它實際上打破了通過引用的傳遞。固定。 – Quentin

+0

'common_type'確定它是否存在一個可以隱式轉換所有其他類型的公共類型。這並不意味着它們都屬於同一類型。 – skypjack

+0

@skypjack OP中的最後一段表示這是所需的行爲。 – Quentin

7

以下是您可以在static_assertstd::enable_if空閒時使用的類型特徵。

template <class T, class ... Ts> 
struct are_all_same : conjunction<std::is_same<T, Ts>...>{}; 

template <class Ts...> 
struct conjunction : std::true_type{}; 

template <class T, class ... Ts> 
struct conjunction<T, Ts...> : 
    std::conditional<T::value, conjunction<Ts...>, std::false_type>::type {}; 

它很簡單地檢查每個類型與第一個類型,如果有任何不同,將失敗。

我想用std::common_type會是這個樣子:

template <class ... Args> 
    typename std::common_type<Args...>::type common_type_check(Args...); 

    void common_type_check(...); 

    template <class ... Ts> 
    struct has_common_type : 
     std::integral_constant< 
      bool, 
      !std::is_same<decltype(common_type_check(std::declval<Ts>()...)), void>::value> {}; 

然後,你可以做static_assert(std::has_common_type<Derived, Base>::value, "")

當然,這種方法並非萬無一失爲common_type有一定的限制,當涉及到基類:

struct A {}; 
struct B : A{}; 
struct C : A{}; 
struct D : C{}; 
struct E : B{}; 

static_assert(has_common_type<E, D, C, A, B>::value, ""); //Fails 
static_assert(has_common_type<A, B, C, D, E>::value, ""); //Passes 

這是因爲模板首先嚐試獲取之間的通用類型和E(即,auto a = bool() ? D{}: E{};無法編譯)。

2

假設只有在每個參數具有相同類型的情況下調用我的函數纔有意義 - 但任何數量的參數都可以。

在這種情況下,爲什麼不使用std::initializer_list

template <typename T> 
void func(std::initializer_list<T> li) { 
    for (auto ele : li) { 
     // process ele 
     cout << ele << endl; 
    } 
} 

由於@Yakk註釋中,你可能希望避免const副本。在這種情況下,你可以指針複製到std::initializer_list

// Only accept pointer type 
template <typename T> 
void func(std::initializer_list<T> li) { 
    for (auto ele : li) { 
     // process pointers, so dereference first 
     cout << *ele << endl; 
    } 
} 

或者專門func爲指針:

// Specialize for pointer 
template <typename T> 
void func(std::initializer_list<T*> li) { 
    for (auto ele : li) { 
     // process pointers, so dereference first 
     cout << *ele << endl; 
    } 
} 

my_struct a, b, c; 
func({a, b, c}); // copies 
func({&a, &b, &c}); // no copies, and you can change a, b, c in func 
+0

限制爲'const'數據拷貝。 – Yakk

+0

你可以複製指向'std :: initializer_list'的指針 –

3

這需要一個任意In類型和在移動它的R /左值參考岬到Out鍵入一個隱式轉換。

template<class Out> 
struct forward_as { 
    template<class In, 
    std::enable_if_t<std::is_convertible<In&&,Out>{}&&!std::is_base_of<Out,In>{},int>* =nullptr 
    > 
    Out operator()(In&& in)const{ return std::forward<In>(in); } 
    Out&& operator()(Out&& in)const{ return std::forward<Out>(in); } 
    template<class In, 
    std::enable_if_t<std::is_convertible<In&,Out&>{},int>* =nullptr 
    > 
    Out& operator()(In& in)const{ return in; } 
    template<class In, 
    std::enable_if_t<std::is_convertible<In const&,Out const&>{},int>* =nullptr 
    > 
    Out const& operator()(In const& in)const{ return in; } 
}; 

有了這個,這是我們正元apply_visitor

template <typename V, typename ... Ss, 
    decltype(std::void_t< 
    std::result_of_t<forward_as<my_struct>(Ss)>... 
    >(),int())* =nullptr 
> 
void apply_visitor(V && v, Ss && ... ss) { 
    auto convert = forward_as<my_struct>{}; 

    std::forward<V>(v)("foo", (convert(std::forward<Ss>(ss)).foo)...); 
    std::forward<V>(v)("bar", (convert(std::forward<Ss>(ss)).bar)...); 
    std::forward<V>(v)("baz", (convert(std::forward<Ss>(ss)).baz)...); 
} 

其不匹配,如果forward_as<my_struct>未能在Ss評估。

live example

+0

我不認爲你可以構造一個'void_t',因爲它只是'void'的一個別名 – SirGuy

+0

@GuyGreer void()'是有效的。不確定是否void {}',所以我刪除它。現在有一個實例,至少有2個編譯器喜歡它。 – Yakk

7

你真正想要的是一樣的東西:

template<typename T, T ... args> 
void myFunc(T ... args); 

但顯然上面是不合法的語法。但是,您可以解決此問題,並使用模板using來幫助您。這樣的想法是這樣的:以上

template<typename T, size_t val> 
using IdxType = T; 

沒有真正的目的:一個IdxType<T, n>僅僅是任何一個nT。但是,它可以讓你做到這一點:

template<typename T, size_t ... Indices> 
void myFunc(IdxType<T, Indices> ... args); 

這是偉大的,因爲這正是你需要獲得一個可變參數組同樣類型的參數是什麼。唯一的問題是,你不能做像myFunc(obj1, obj2, obj3)這樣的東西,因爲編譯器將無法推導出所需的Indices - 你將不得不做myFunc<1,2,3>(obj1, obj2, obj3),這很醜陋。幸運的是,您可以通過使用make_index_sequence包裝輔助函數來避免這種情況,該函數負責爲您生成索引。

下面是一個完整的例子,它類似於您的訪客的東西(現場演示here):

template<typename T, size_t sz> 
using IdxType = T; 

struct MyType 
{}; 

struct Visitor 
{ 
    void operator() (const MyType&) 
    { 
     std::cout << "Visited" << std::endl; 
    } 
}; 

template <typename V> 
void apply_visitor(std::index_sequence<>, V && v) 
{ 
} 

template <typename V, typename T, size_t FirstIndex, size_t ... Indices> 
void apply_visitor(std::index_sequence<FirstIndex, Indices...>, V && v, T && first, IdxType<T, Indices> && ... ss) { 
    std::forward<V>(v)(std::forward<T>(first)); 
    apply_visitor(std::index_sequence<Indices...>(), std::forward<V>(v), std::forward<T>(ss) ...); 
} 

template <typename V, typename T, typename ... Rest> 
void do_apply_visitor(V && v, T && t, Rest && ... rest) 
{ 
    apply_visitor(std::make_index_sequence<sizeof...(Rest)+1>(), v, t, rest ...); 
} 

int main() 
{ 
    Visitor v; 

    do_apply_visitor(v, MyType{}, MyType{}, MyType{}); 

    return 0; 
} 
+0

這是一個不錯的方法,我甚至沒有想到這樣做 –

-3

我覺得你可以發揮這樣的功能,並檢查你函數內的參數。

template <typename T, typename... Args> bool check_args(T a, Args args) 
{ 
static string type; 
if(type == "") type = typeid(a).name; 
else if(type != typeid(a).name) return false; 
else return check_args(args...); 
} 
bool check_args() {return true;} 
+1

這將不允許檢查'static_assert'或禁用與'enable_if'重載 – SirGuy

0

一個可能的解決方案是在下面的例子中使用編譯時函數像are_same

template <typename... Ts> 
void func(Ts &&... ts) { 
    static_assert(are_same<Ts...>(), "!"); 
    step_one(std::forward<Ts>(ts)...); 
    step_two(std::forward<Ts>(ts)...); 
} 

你將會得到一個很好:

#include <type_traits> 

template<typename T, typename... O> 
constexpr bool are_same() { 
    bool b = true; 
    int arr[] = { (b = b && std::is_same<T, O>::value, 0)... }; 
    return b; 
} 

int main() { 
    static_assert(are_same<int, int, int>(), "!"); 
    static_assert(not are_same<int, double, int>(), "!"); 
} 

,因爲它遵循使用它按要求編譯時間錯誤消息。

相關問題