4

子を値で保持する式テンプレートメカニズムのCRTPの標準的な使用を検討してください。

template <typename T, typename>
struct Expr {};

template <typename T>
struct Cst : Expr<T, Cst<T>> 
{
    Cst(T value) : value(std::move(value)) {}

private:
    T value;
};

template <typename T, typename L, typename R>
struct Add : Expr<T, Add<T, L, R>> 
{
    Add(L l, R r) : l(std::move(l)), r(std::move(r))

private:
    L l; R r;
};

ここで、演算子を実装するとき、引数は正しい型にダウンキャストされるため、参照で渡す必要があります。問題は、次の4つの(!)バージョンを実装していることに気付くということですoperator+

template <typename T, typename L, typename R>
Add<T, L, R> operator+(Expr<T, L>&& l, Expr<T, R>&& r)
{
    return Add<T, L, R>(
        std::move(static_cast<L&>(l)),
        std::move(static_cast<R&>(r)));
}

template <typename T, typename L, typename R>
Add<T, L, R> operator+(const Expr<T, L>& l, Expr<T, R>&& r)
{
    return Add<T, L, R>(
        static_cast<const L&>(l),
        std::move(static_cast<R&>(r)));
}

template <typename T, typename L, typename R>
Add<T, L, R> operator+(Expr<T, L>&& l, const Expr<T, R>& r)
{
    return Add<T, L, R>(
        std::move(static_cast<L&>(l)),
        static_cast<const R&>(r));
}

template <typename T, typename L, typename R>
Add<T, L, R> operator+(const Expr<T, L>& l, const Expr<T, R>& r)
{
    return Add<T, L, R>(
        static_cast<const L&>(l),
        static_cast<const R&>(r));
}

実際、不必要なコピーを最小限に抑えることが目標である場合、一時的なもの(移動可能)と左辺値(コピーする必要がある)を区別する必要があるため、4つのオーバーロードが発生します。

C ++ 03では、「問題ありません」。const参照を使用し、常にコピーします。C ++ 11では、より良い結果が得られます。それがここでの目標です。

追加ロジックを一度書くことができるトリックはありますか、それともここでマクロを書くのが最善のオプションです(ロジックは他の演算子に対して繰り返されるため)?

また、C++11を使用して式テンプレートを作成する方法に関する他の提案も受け付けています。ターミナルノードに格納される値は膨大な数または行列になる可能性があるため、目標はコピーを最小限に抑えることであると考えてください(私の正確なケースでは、ターミナルノードには数メガバイトの補間データが含まれている可能性があり、これらのオブジェクトのコピーは無効になっています--他のオブジェクト、コピーが可能です)。

4

2 に答える 2

6

引数を値で渡すことができる式テンプレートを作成する別のアプローチを次に示します。

template <typename T>
struct Expr : T {
  Expr(T value) : T(value) { }
};

template <typename A,typename B>
struct Add {
  A a;
  B b;

  Add(A a,B b) : a(a), b(b) { }
};

template <typename A,typename B>
Expr<Add<A,B> > operator+(Expr<A> a,Expr<B> b)
{
  return Expr<Add<A,B> >(Add<A,B>(a,b));
}

暗黙のコピーはたくさんありますが、コンパイラーがそれらを削除する優れた仕事をしていることがわかりました。

また、定数の使用を便利にするために、追加のオーバーロードを記述できます。

template <typename A,typename B>
Expr<Add<Constant<A>,B> > operator+(const A& a,Expr<B> b)
{
  return Expr<Add<Constant<A>,B> >(Add<Constant<A>,B>(a,b));
}

template <typename A,typename B>
Expr<Add<A,Constant<B> > > operator+(Expr<A> a,const B& b)
{
  return Expr<Add<A,Constant<B> > >(Add<A,Constant<B> >(a,b));
}

ここで、Constantは次のようなクラステンプレートです。

template <typename T>
struct Constant {
  const T& value;
  Constant(const T& value) : value(value) { }
};

暗黙のコピーはたくさんありますが、コンパイラーがそれらを削除する優れた仕事をしていることがわかりました。

于 2012-07-29T18:30:11.140 に答える
2

オブジェクトはコメントに従って移動するのが安価なので、operator+値によってtake引数を作成し、コンパイラに、呼び出しサイトでコピーをどれだけ回避できるかを計算させます。スライスを避けるためoperator+には、派生型で作業する必要があることを意味します(やや過度にバインドすることになりますoperator+)。それを制御するには、おそらくstd::enable_if次のようなものを使用する必要があります。

template <typename T, typename U>
struct Expr {
    typedef T expr_type;//added for getting T in the enable_if. Could probably also behandled with a custom type trait
};

template <typename L, typename R>
typename std::enable_if<std::is_base_of<Expr<typename L::expr_type, L>, L>::value &&
                        std::is_base_of<Expr<typename L::expr_type, R>, R>::value, 
                        Add<typename L::expr_type, L, R>>::type
operator+(L l, R r) {
    return Add<typename L::expr_type, L, R>(std::move(l), std::move(r));
}

もちろん、それをより頻繁に使用する場合は、特性の状態をカプセル化することをお勧めします。これにより、次のようなものが得られます。

template <typename L, typename R, typename T>
struct AreCompatibleExpressions {
    static constexpr bool value = std::is_base_of<Expr<T, L>, L>::value &&
                                  std::is_base_of<Expr<T, R>, R>::value;
};

template <typename L, typename R>
typename std::enable_if<AreCompatibleExpressions<L, R, typename L::expr_type>::value,
                        Add<typename L::expr_type, L, R>>::type
operator+(L l, R r) {
    return Add<typename L::expr_type, L, R>(std::move(l), std::move(r));
}

さらに簡潔にするために、独自EnableIfCompatibleExpressionsに書くこともできますが、それは少しやり過ぎのimoのようです。

補足として:のコンストラクターにバグがありAddます。そのはず

Add(L left, R right) : l(std::move(left)), r(std::move(right))
于 2012-07-29T18:30:39.593 に答える