6

wrapper別の typeの型を書き込もうとして、かなり厄介な問題に遭遇しました: 基本となる型に操作を転送Tするいくつかの二項演算子 ( など) を定義したいのですが、これらの演算子が潜在的なものを受け入れる必要があります。を含む組み合わせ:+wrapperwrapper

wrapper() + wrapper()
wrapper() + T()
T()       + wrapper()

単純なアプローチでは、潜在的なオーバーロードをすべて直接記述します。

しかし、私は重複したコードを書くのが好きではなく、もう少し挑戦したかったので、非常に一般的なテンプレートを使用して実装し、enable_if.

私の試みは質問の最後に示されています(申し訳ありませんが、これは私が考えることができる限り最小限です)。問題は、無限再帰エラーが発生することです。

  1. を評価するためtest() + test()に、コンパイルはすべての潜在的なオーバーロードを調べます。
  2. ここで定義された演算子は、実際には潜在的なオーバーロードであるため、戻り値の型を構築しようとします。
  3. 戻り値の型にはenable_if、それが有効なオーバーロードになるのを防ぐことになっている句がありますが、コンパイラはそれを無視して、decltype最初の値を計算しようとします。これには ... が必要です。
  4. ... のインスタンス化operator+(test, test)

そして、私たちは出発点に戻りました。GCC はエラーを吐き出すのに十分です。Clang は単にセグメンテーション違反です。

これに対する適切でクリーンな解決策は何でしょうか? (同じパターンに従う必要がある他の演算子もあることに注意してください。)

template<class T>
struct wrapper { T t; };

// Checks if the type is instantiated from the wrapper
template<class>   struct is_wrapper              : false_type {};
template<class T> struct is_wrapper<wrapper<T> > : true_type  {};

// Returns the underlying object
template<class T> const T& base(const T& t)          { return   t; }
template<class T> const T& base(const wrapper<T>& w) { return w.t; }

// Operator
template<class W, class X>
typename enable_if<
    is_wrapper<W>::value || is_wrapper<X>::value,
    decltype(base(declval<W>()) + base(declval<X>()))
>::type operator+(const W& i, const X& j);

// Test case
struct test {};
int main() {
    test() + test();
    return 0;
}

これは、必要がない限り使用したくない、かなり不格好なソリューションです。

// Force the evaluation to occur as a 2-step process
template<class W, class X, class = void>
struct plus_ret;
template<class W, class X>
struct plus_ret<W, X, typename enable_if<
    is_wrapper<W>::value || is_wrapper<X>::value>::type> {
    typedef decltype(base(declval<W>()) + base(declval<X>())) type;
};

// Operator
template<class W, class X>
typename plus_ret<W, X>::type operator+(const W& i, const X& j);
4

4 に答える 4

2

これは、間違いなく同様の状況で、のブーストページで触れられているものですenable_if(ただし、回避したいエラーは異なります)。ブーストの解決策は、lazy_enable_ifクラスを作成することでした。

問題は、コンパイラが関数シグネチャに存在するすべての型をインスタンス化し、したがってdecltype(...)式もインスタンス化しようとすることです。また、型の前に条件が計算されるという保証もありません。

残念ながら、この問題の解決策を思いつきませんでした。私の最新の試みはここで見ることができ、まだインスタンス化の深さの最大の問題を引き起こしています。

于 2013-08-06T07:05:41.063 に答える
2

TemplateRex のコメントへの追加として、マクロを使用してすべてのオーバーロードを実装し、演算子を引数として取ることをお勧めします。

template<class T>
struct wrapper { T t; };

#define BINARY_OPERATOR(op)                                      \
  template<class T>                                              \
  T operator op (wrapper<T> const& lhs, wrapper<T> const& rhs);  \
  template<class T>                                              \
  T operator op (wrapper<T> const& lhs, T const& rhs);           \
  template<class T>                                              \
  T operator op (T const& lhs, wrapper<T> const& rhs); 

BINARY_OPERATOR(+)
BINARY_OPERATOR(-)

#undef BINARY_OPERATOR

// Test case
struct test {};
test operator+(test const&, test const&);
test operator-(test const&, test const&);

int main() {
    test() + test();
    wrapper<test>() + test();
    test() - wrapper<test>();
    return 0;
}
于 2013-08-06T06:59:04.090 に答える
1

混合モード演算を記述する最も簡単な方法は、Scott Meyers の Item 24 in Effective C++に従うことです。

template<class T>
class wrapper1 
{ 
public:
    wrapper1(T const& t): t_(t) {} // yes, no explicit here

    friend wrapper1 operator+(wrapper1 const& lhs, wrapper1 const& rhs)
    {
        return wrapper1{ lhs.t_ + rhs.t_ };        
    }

    std::ostream& print(std::ostream& os) const
    {
        return os << t_;
    }

private:
    T t_; 
};

template<class T>
std::ostream& operator<<(std::ostream& os, wrapper1<T> const& rhs)
{
    return rhs.print(os);
}

operator+=一貫したインターフェースを提供するためには、まだ書く必要があることに注意してください( 「do as the ints do」 )。そのボイラープレートも避けたい場合は、Boost.Operatorsを見てください。

template<class T>
class wrapper2
:
    boost::addable< wrapper2<T> >
{ 
public:
    wrapper2(T const& t): t_(t) {}

    // operator+ provided by boost::addable
    wrapper2& operator+=(wrapper2 const& rhs)
    {
        t_ += rhs.t_;
        return *this;
    }        

    std::ostream& print(std::ostream& os) const
    {
        return os << t_;
    }

private:
    T t_; 
};

template<class T>
std::ostream& operator<<(std::ostream& os, wrapper2<T> const& rhs)
{
    return rhs.print(os);
}

どちらの場合でも、次に書くことができます

int main()
{
    wrapper1<int> v{1};
    wrapper1<int> w{2};

    std::cout << (v + w) << "\n";
    std::cout << (1 + w) << "\n";
    std::cout << (v + 2) << "\n";

    wrapper2<int> x{1};
    wrapper2<int> y{2};

    std::cout << (x + y) << "\n";
    std::cout << (1 + y) << "\n";
    std::cout << (x + 2) << "\n";

}

すべての場合に 3 を出力します。実例。Boost アプローチは非常に一般的です。たとえば、 から派生して の定義からboost::arithmetic提供することもできます。operator*operator*=

T: このコードは、から への暗黙的な変換に依存していwrapper<T>ます。しかし、Scott Meyers を引用すると、次のようになります。

暗黙的な型変換をサポートするクラスは、一般に悪い考えです。もちろん、この規則には例外があり、最も一般的なのは数値型を作成する場合です。

于 2013-08-06T07:10:56.753 に答える