2016-07-22 4 views
24

我々は、引数の任意の順序を受け入れることができ、テンプレート機能を加えることができます、しかし単一のタイプのインスタンスを持つように制約されたC++のパラメータパック? C++ 11以降

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

をそれは本当に唯一の各引数は、同じ型を持っている場合には、私の関数を呼び出すことは理にかなっているとし - 引数はいくつでもOKです。

その場合、最適な方法は、テンプレートがその場合にエラーメッセージを表示するようにテンプレートを制約するか、引数が一致しないときに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の2つのインスタンスを訪れることができるように、今、私はそれを拡張したいと思います同時に。それを使用するのは、等価または比較操作を実装する場合です。 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)...); 
} 

を推測しかし、今、それはもう少しsquirrellyなってきた - 誰かがすべてでさえも同じ構造のタイプではありません種類のシリーズを通過すれば、コードはまだコンパイルしますユーザーがまったく予期せぬことをする。

私はこのようにそれをやって考えた:

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

彼らはミスマッチの種類とそれを使用する場合、少なくともコンパイルエラーの原因となること。しかし、それはまた遅すぎて起こっています - テンプレートタイプが解決された後に起こっています、そして、オーバーロード解決の後、私は推測します。

voidを返す代わりにstd::enable_if_tを使用し、パラメータパックの各タイプについて式std::is_same<std::remove_cv_t<std::remove_reference_t<...>>を調べて、SFINAEを使用することを考えました。

SFINAEの式はかなり複雑で、2つの理由で欠点もあります。派生クラスstruct my_other_struct : my_struct { ... }があり、ビジターのメカニズムで使用したいと考えているため、一部のパラメータmy_structであり、一部はmy_other_structです。理想的には、システムはすべての参照をmy_structに変換し、その方法で訪問者を適用し、afaikはメンバーポインタfoo_ptrbar_ptrbaz_ptrで上記の例を示しましたが、そこに正しいことをするでしょうが、 SFINAEのような制約 - 私が推測するすべてのパラメータの共通基盤を見つけることを試みなければならないだろうか?

これらの懸案事項を一般的に調整する良い方法はありますか?

+4

'std :: common_type'? – cpplearner

+0

あなたはそれらがすべて特定の既知のタイプであること、あるいは同じ、未知のタイプであることを希望しますか? – Quentin

+0

@cpplearner:ああ、それはとてもいいですね、私はそれについては知らなかった –

答えて

15

、これは簡単です:

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

} 

これが唯一のはしかし、以降C++17からやさしいSFINAEするを保証されます。 ClangGCCは両方ともそのように、既にそれを実装します。

+0

@ JohanLundberg私はパラメータ渡しをエミュレートするためにそこに置いていましたが、いくつかのテストの後、実際には参照渡しが破られました。一定。 – Quentin

+0

'common_type'は、他の全ての暗黙的な変換が可能な共通型が存在するかどうかを判断します。これは、それらがすべて同じタイプであることを意味するものではありません。 – skypjack

+0

@skypjack OPの最後の段落は、これが望ましい動作であることを示しています。 – Quentin

7

ここでは、static_assertまたはstd::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を使用して思う: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, "")

行うことができますが、この方法はきわめて簡単ではありません:

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 

これは、テンプレートが最初に共通タイプをDE(すなわち、auto a = bool() ? D{}: E{};はコンパイルに失敗しました)。 std::common_type

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タイプを取り、OutにそのR /左辺値参照ネスを超える移動します暗黙のキャストを入力します。forward_as<my_struct>Ssに評価するために失敗した場合に一致しない

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

:これにより

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

は、ここで私たちのn進apply_visitorです。

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>はただのnためTです。しかし、それはあなたがこれを行うことができます:素晴らしいです

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

を、これは正確にあなたが同様に型指定されたパラメータの可変引数セットを取得するために必要なものであるとして。残っている唯一の問題は、コンパイラが要求されたIndicesを推論することができないので、myFunc(obj1, obj2, obj3)のようなことをすることができないということです - あなたは醜い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のようなコンパイル時の機能を使用して次のようになります。それは次のように

#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>(), "!"); 
} 

は、それを使用します。

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

あなたは素敵なを持っていますコンパイル時にエラーメッセージが要求通りに出力されます。

関連する問題