20

次のコードがコンパイルされないのはなぜだろうと思います。

struct S
{
    template <typename... T>
    S(T..., int);
};

S c{0, 0};

このコードは、clangとGCC4.8の両方でコンパイルできません。clangのエラーは次のとおりです。

test.cpp:7:3: error: no matching constructor for initialization of 'S'
S c{0, 0};
  ^~~~~~~
test.cpp:4:5: note: candidate constructor not viable: requires 1 argument, but 2 were provided
    S(T..., int);
    ^

これはうまくいくはずであり、Tは長さ1のパックであると推定されるべきであるように私には思えます。

基準がこのようなことを禁止している場合、誰かがその理由を知っていますか?

4

4 に答える 4

10

関数パラメーターパックが最後のパラメーターでない場合、テンプレートパラメーターパックをそこから推定することはできず、テンプレート引数の推定によって無視されるためです。

したがって、2つの引数0, 0がと比較さ, intれ、不一致が生じます。

このような控除ルールは、多くの特殊なケース(2つのパラメーターパックが隣り合って表示される場合など)をカバーする必要があります。パラメータパックはC++11の新機能であるため、それぞれの提案の作成者は保守的にルールを作成しました。

他の方法で推測されない場合、末尾のテンプレートパラメータパックは空になることに注意してください。したがって、1つの引数を指定してコンストラクターを呼び出すと、動作します(ここでは、テンプレートパラメーターパックと関数パラメーターパックの違いに注意してください。前者は末尾にあり、後者はそうではありません)。

于 2013-02-09T12:22:31.437 に答える
6

したがって、回避策が必要です。これらの線に沿った何か:

namespace v1 {
  // Extract the last type in a parameter pack.
  // 0, the empty pack has no last type (only called if 1 and 2+ don't match)
  template<typename... Ts>
  struct last_type {};

  // 2+ in pack, recurse:
  template<typename T0, typename T1, typename... Ts>
  struct last_type<T0, T1, Ts...>:last_type<T1, Ts...>{};

  // Length 1, last type is only type:
  template<typename T0>
  struct last_type<T0> {
    typedef T0 type;
  };
}
namespace v2 {
  template<class T> struct tag_t{using type=T;};
  template<class T> using type_t = typename T::type;
  template<class...Ts>
  using last = type_t< std::tuple_element_t< sizeof...(Ts)-1, std::tuple<tag_t<Ts>...> > >;
  template<class...Ts>
  struct last_type {
    using type=last<Ts...>;
  };
}
template<class...Ts>
using last_type=v2::late_type<Ts...>; // or v1   


struct S
{
    // We accept any number of arguments
    // So long as the type of the last argument is an int
    // probably needs some std::decay to work right (ie, to implicitly work out that
    // the last argument is an int, and not a const int& or whatever)
    template <typename... T, typename=typename std::enable_if<std::is_same<int, typename last_type<T...>::type>>::type>
    S(T...);

};

ここで、パラメータパックの最後のタイプがであるintか、またはを渡しただけであるかを確認しますint

于 2013-02-08T21:40:41.260 に答える
3

私は実際には同じことに少し興味があります(最後の引数に基づいてテンプレート化されたパラメーターパックを特殊化したいと思っています)。

タプルの反転( 、 C ++ 14のstd::make_tupleバックポートなど)を組み合わせることで、前進する道があると思います。std::apply

成功すればここに戻ります。

関連記事:

編集:うん、少し後にそれを理解した。余分なコピーが飛び交っているので完璧ではありませんが、それは始まりです。

以下にリストするよりも簡単な方法をご存知の場合は、遠慮なく投稿してください。

TL; DR

このようなことを行うことができます:

auto my_func_callable = [] (auto&& ... args) {
    return my_func(std::forward<decltype(args)>(args)...);
};
auto my_func_reversed =
    stdcustom::make_callable_reversed(my_func_callable);

そして、このpseduoコードを実装します。

template<typename ... Args>
void my_func(Args&& ... args, const my_special_types& x);

次のようなことを行うことによって:

template<typename... Args>
void my_func(Args&& ... args)
    -> call my_func_reversed(args...)
template<typename... RevArgs>
void my_func_reversed(const my_special_types& x, RevArgs&&... revargs)
    -> do separate things with revargs and my_special_types
    -> sub_func_reversed(revargs...)

上記のユーティリティを使用します。

いくつかの(多くの)欠点があります。それらを以下にリストします。

範囲

これは、将来(C ++ 17)から借りたいC ++ 14(おそらくC ++ 11)のユーザー向けです。

ステップ1:引数を逆にする

これを行うには、いくつかの異なる方法があります。この例では、いくつかの選択肢をリストアップしました。

  • tuple.cc -2つの選択肢の遊び場(ソースコードのクレジット):
    1. 折りたたみ式を使用し、std::apply_impl(クレジット:Orient)を介して渡されたインデックスを操作します。
    2. 再帰テンプレートを使用して、反転を作成しますindex_sequence(クレジット:Xeo)
  • tuple.output.txt-出力例

    • これによりreversed_index_sequence、Xeoの例からテンプレートが出力されます。デバッグのためにこれが必要でした。

      >>> name_trait<std::make_index_sequence<5>>::name()
      std::index_sequence<0, 1, 2, 3, 4>
      >>> name_trait<make_reversed_index_sequence<5>>::name()
      std::index_sequence<4, 3, 2, 1, 0>
      

消化しやすいので、Alternative1を選びました。それから私はそれをすぐに形式化しようとしました:

定義スニペット(cppreference.comでのC ++ 17の可能な実装のstd::apply適応):

namespace detail {
template <class F, class Tuple, std::size_t... I>
constexpr decltype(auto) apply_reversed_impl(F &&f,
    Tuple &&t, std::index_sequence<I...>) 
{
    // @ref https://stackoverflow.com/a/31044718/7829525
    // Credit: Orient
    constexpr std::size_t back_index = sizeof...(I) - 1;
    return f(std::get<back_index - I>(std::forward<Tuple>(t))...);
}
} // namespace detail
template <class F, class Tuple>
constexpr decltype(auto) apply_reversed(F &&f, Tuple &&t) 
{
    // Pass sequence by value to permit template inference
    // to parse indices as parameter pack
    return detail::apply_reversed_impl(
        std::forward<F>(f), std::forward<Tuple>(t),
        std::make_index_sequence<
            std::tuple_size<std::decay_t<Tuple>>::value>{});
}

使用法スニペット:(からtuple_future_main.output.txt、上からコピー)

auto my_func_callable = [] (auto&& ... args) {
    return my_func(std::forward<decltype(args)>(args)...);
};
auto my_func_reversed =
    stdcustom::make_callable_reversed(my_func_callable);

ステップ2:靴を締める(パラメーターパックを逆にして)

まず、使用する最後の引数のパターンを確立します。パラメータパックは1つしか持てないため、これらを明示的に列挙する必要があります。

tuple_future_main.ccから取得):

シナリオ例:

私たちは、次のような名前のコンテナに物を追加するのが好きです。

add_item(const Item& item, const string& name, Container& c)

[非常に多くの]オーバーロードを含むアイテムを作成することもでき、便利なオーバーロードがあります。

add_item(${ITEM_CTOR_ARGS}, const string& name, Container& c)

そのために、次のことを宣言できます。

void add_item_direct(const Item& item, const string& name, Container& c)
Item create_item(Args&&... args)

次に、ジェネリックインターフェイスを定義します。

template<typename... Args>
void add_item(Args&&... args) {
    ...
    auto reversed = stdcustom::make_callable_reversed(callable);
    reversed(std::forward<Args>(args)...);
}
template<typename ... RevArgs>
void add_item_reversed(Container& c, const string& name, RevArgs&&... revargs)
{
    ...
    static auto ctor = VARIADIC_CALLABLE(create_item,);
    ...
    auto item = ctor_reversed(std::forward<RevArgs>(revargs)...);
    add_item_direct(item, name, c);
}

今、私たちは次のようなことをすることができます:(から取得tuple_future_main.output.txt

>>> (add_item(Item("attribute", 12), "bob", c));
>>> (add_item("attribute", 12, "bob", c));
>>> (add_item(Item(2, 2.5, "twelve"), "george", c));
>>> (add_item(2, 2.5, "twelve", "george", c));
>>> (add_item(Item(2, 15.), "again", c));
>>> (add_item(2, 15., "again", c));
>>> c
bob - ctor3: ctor3: ctor1: attribute (12, 10)
bob - ctor3: ctor1: attribute (12, 10)
george - ctor3: ctor3: ctor2: 2, 2.5 (twelve)
george - ctor3: ctor2: 2, 2.5 (twelve)
again - ctor3: ctor3: ctor2: 2, 15 ()
again - ctor3: ctor2: 2, 15 ()

余分なコピーコンストラクターに注意してください...:(

欠点

  • 地獄のように醜い
  • 役に立たないかもしれません
    • インターフェイスをリファクタリングする方が簡単かもしれません
      • ただし、これは、より一般化されたインターフェイスに移行するための一時的なギャップとして使用できます。
      • 削除する行が少なくなる可能性があります。
    • 特に、開発プロセスがテンプレートの爆発的な増加につながる場合
  • 余分なコピーがどこから来ているのかを特定することはできません。
    • 可変個引数ラムダの賢明な使用が原因である可能性があります
  • 基本機能を慎重に作成する必要があります
    • 既存の関数を拡張しようとしないでください。
    • パラメータパックは、関数との一致に貪欲になります
    • 必要な各オーバーロードを明示的に説明するか、お辞儀をして可変個引数パラメーターパックを目的の機能にディスパッチさせる必要があります。
      • これを回避するためのエレガントな方法を見つけたら、私に知らせてください。
  • テンプレートエラーはくだらないです。
    • 確かに、くだらないことではありません。しかし、利用可能な過負荷を逃したと推測するのは難しいです。
  • 多くの単純な関数をラムダでラップします
    • make_reversed_index_sequence関数を使用して直接ディスパッチできる場合があります(他のSOの投稿に記載されています)。しかし、それを繰り返すのは苦痛です。

Todo

  • 余分なコピーを取り除く
  • すべてのラムダの必要性を最小限に抑える
    • あなたが持っている場合は必要ありませんCallable
  • パラメータパックの貪欲と戦ってみてください

    • std::enable_if左辺値参照と右辺値参照の両方に一致し、互換性のある暗黙的なコピーコンストラクターの転送を処理する可能性のある一般化された一致はありますか?

      template<typename ... Args>
      void my_func(Args&& ... args) // Greedy
      void my_func(magical_ref_match<string>::type, ...)
          // If this could somehow automatically snatch `const T&` and `T&&` from the parameter pack...
          // And if it can be used flexible with multiple arguments, combinatorically
      

希望

  • たぶん、C ++ 17は非最終的なパラメータパック引数をサポートするので、これらすべてを破棄することができます...指が交差しました
于 2017-04-19T01:52:08.450 に答える
2

標準N3376§14.1の作業草案から、これについて読む可能性のあるセクションがあります。

以下は§14.1.11です

クラステンプレートまたはエイリアステンプレートのtemplate-parameterにデフォルトのtemplate-argumentがある場合、後続の各template-parameterには、デフォルトのtemplate-argumentが提供されるか、テンプレートパラメーターパックである必要があります。プライマリクラステンプレートまたはエイリアステンプレートのテンプレートパラメータがテンプレートパラメータパックである場合、それが最後のテンプレートパラメータになります。関数テンプレートのテンプレートパラメータパックの後に、そのテンプレートパラメータが関数テンプレートのパラメータタイプリストから推測できるか、デフォルトの引数がない限り、別のテンプレートパラメータを続けてはなりません。

于 2013-02-08T09:09:45.663 に答える