1

タイプ A と B の 2 つの任意のデータ メンバーを持つクラス Foo があります。Foo::operator()(Arg &&) を呼び出すと、引数が 2 つのメンバーに転送され、結果の合計が返されます。必要な型推論をすべて実装する方法はいくつかあります。推奨され、コンパイラへの負担を軽減する方法はありますか? コンパイル時の意味での「ひずみ」、入れ子が深すぎると内部制限に達するなどを意味します。それを一般化できますか、それとも特定のコンパイラに非常に固有ですか?

「素朴な」auto-decltypeバリアントを実行できます。

template <typename A, typename B>
class Foo
{
public:
  Foo(A a, B b) : m_a(std::move(a)), m_b(std::move(b)) { }

  template <typename Arg>
  auto operator()(Arg && arg) -> decltype(m_a(std::forward<Arg>(arg)) + m_b(std::forward<Arg>(arg)))
  {
    return m_a(std::forward<Arg>(arg)) + m_b(std::forward<Arg>(arg));
  }
private:
  A m_a;
  B m_b;
};

「実際の」インスタンスではなく、 std::declval<> によって作成された型でのみ動作するヘルパー構造体を作成できます。

template <typename A, typename B, typename Arg>
struct Foo_Returns
{
  typedef decltype(std::declval<A>()(std::declval<Arg>()) +
                   std::declval<B>()(std::declval<Arg>())) type;
}

template <typename A, typename B>
class Foo
{
public:
  Foo(A a, B b) : m_a(std::move(a)), m_b(std::move(b)) { }

  template <typename Arg>
  typename Foo_Returns<A, B, Arg>::type
  operator()(Arg && arg)
  {
    return m_a(std::forward<Arg>(arg)) + m_b(std::forward<Arg>(arg));
  }
private:
  A m_a;
  B m_b;
};

これ以上の可能性はありますか?

ここでさらに難しくしましょう: is_green<> と is _blue<> という 2 つのトレイトがあります。Arg が緑の場合、Foo の operator() は Arg を A と B のメンバー関数 green に転送し、Arg が青の場合と同様に、結果の合計を返します。タイプが緑と青の両方になることはありません。タイプの追加フレーバーを追加できる必要があります (そのため、bool 値を使用して青または緑を示すことは許可されていません)。

1 つのバリアントは、可能な限りタグ ディスパッチと auto -> decltype(...) を使用します。

struct green_tag { };
struct blue_tag { };
struct error_tag;

template <typename T>
struct category
{
  typedef typename std::conditional<is_green<T>::value, 
                                    green_tag,
                                    typename std::conditional<is_blue<T>::value,
                                                              blue_tag,
                                                              error_tag
                                                             >::type
                                   >::type type;
}

template <typename A, typename B>
class Foo
{
public:
  Foo(A a, B b) : m_a(std::move(a)), m_b(std::move(b)) { }

  template <typename Arg>
  auto operator()(Arg && arg) -> decltype(impl(std::forward<Arg>(arg), typename category<Arg>::type()))
  {
    return impl(std::forward<Arg>(arg), typename category<Arg>::type());
  }

private:
  template <typename Arg>
  auto impl(Arg && arg, green_tag) -> decltype(m_a.green(std::forward<Arg>(arg)) + m_b.green(std::forward<Arg>(arg)))
  {
    return m_a.green(std::forward<Arg>(arg)) + m_b.green(std::forward<Arg>(arg));
  }

  template <typename Arg>
  auto impl(Arg && arg, blue_tag) -> decltype(m_a.blue(std::forward<Arg>(arg)) + m_b.blue(std::forward<Arg>(arg)))
  {
    return m_a.blue(std::forward<Arg>(arg)) + m_b.blue(std::forward<Arg>(arg));
  }

  A m_a;
  B m_b;
};

別のバージョンでは、ヘルパー構造体を使用できます。

template <typename A, typename B, typename Arg, typename Category = typename category<Arg>::type>
struct Foo_Returns;

template <typename A, typename B, typename Arg>
struct Foo_Returns<A, B, Arg, green_tag>
{
  typedef decltype(std::declval<A>().green(std::declval<Arg>()) +
                   std::declval<B>().green(std::declval<Arg>())) type;

  type operator()(A & a, B & b, Arg && arg) const
  {
    return a.green(std::forward<Arg>(arg)) + b.green(std::forward<Arg>(arg));
  }  
};

template <typename A, typename B, typename Arg>
struct Foo_Returns<A, B, Arg, blue_tag>
{
  typedef decltype(std::declval<A>().blue(std::declval<Arg>()) +
                   std::declval<B>().blue(std::declval<Arg>())) type;

  type operator()(A & a, B & b, Arg && arg) const
  {
    return a.blue(std::forward<Arg>(arg)) + b.blue(std::forward<Arg>(arg));
  }  
};

template <typename A, typename B>
class Foo
{
public:
  Foo(A a, B b) : m_a(std::move(a)), m_b(std::move(b)) { }

  template <typename Arg>
  typename Foo_Returns<A, B, Arg>::type
  operator()(Arg && arg)
  {
    return Foo_Returns<A, B, Arg>()(m_a, m_b, std::forward<Arg>(arg));
  }

private:
  A m_a;
  B m_b;
};

どのバージョンがより良いですか?他にどのような方法が考えられますか?

4

1 に答える 1

0

私はすべてのヘルパー クラス/構造体を避けます。各ヘルパーは、コンパイラーがそれをどこかに保管し、追加のルックアップを行う必要があります。クラスがなければ、コンパイラは少なくとも物事を最適化する機会を得ますが、ヘルパー クラスがあなたが示した例の状況を改善できるとは想像できません。

緑/青の例では、SFINAE を使用してコードを短くし、インスタンス化されたクラス/メソッドの数を少なくすることも検討します。

template <typename A, typename B>
class Foo
{
public:
  Foo(A a, B b) : m_a(std::move(a)), m_b(std::move(b)) { }

  template <typename Arg>
  auto operator()(const Arg & arg) ->
    typename std::enable_if< is_green<Arg>::value,
      decltype(m_a.green(arg) + m_b.green(arg) >::type
  {
    return m_a.green(arg) + m_b.green(arg);
  }

  template <typename Arg>
  auto operator()(const Arg & arg) ->
    typename std::enable_if< is_blue<Arg>::value,
      decltype(m_a.blue(arg) + m_b.blue(arg)) >::type
  {
    return m_a.blue(arg) + m_b.blue(arg);
  }

private:
  A m_a;
  B m_b;
};

また、これはより保守しやすい、YMMV だと思います。コンパイル時のパフォーマンスについては、いつものように 1 つのアドバイスしかありません。測定することです。

編集:これは違法であるため、パラメーターをからに変更しArg&&const Arg&ダブルを削除しました。コメントを参照してください。std::forward<Arg>(...)

于 2013-03-18T17:13:28.207 に答える