3

関数型言語 (OCaml) で以前に書いたコードを C++ に書き直そうとしています。

私の問題は次のように短縮できます。

  • 私は値のスタックを持っています
  • 値はバリアント型にすることができます (したがって、さまざまな種類の値のセット、たとえばintfloatstd::stringなどstd::list)
  • 値に作用する演算子を定義したい (例: 2 つの値をポップしてそれらの合計をプッシュする加算演算)
  • 一部の演算子は、スタック上にある型に応じて異なる動作をします。理想的には、型に応じて引数の数を変更する演算子もあります (単純な例: 加算演算子は、1 つの値をポップし、std::listそれがすべての間に適用された演算子をプッシュする場合)リストの値。それ以外の場合は別の値をポップし、両方が float の場合は加算を行います)

これまでは、テンプレートなどを使用して機能させることができました。

class Value
{
  public:
    Value(Type type) : type(type) { }

    virtual string svalue() const = 0;
    virtual string lvalue();

    virtual bool equals(Value *value) const = 0;
    virtual Value* clone() const = 0;

    const Type type;

    virtual ~Value() { };
}

template <class T>
class TValue : public Value
{
  protected:
    T value;

  public:
    TValue(Type type, T value) : Value(type), value(value) {}

    void set(T value) { this->value = value; }
    T get() const { return this->value; }
};

class Int : public TValue<int>
{
  private:

  public:  
    Int(int value) : TValue<int>(TYPE_INT, value) { };

    virtual string svalue() const;

    virtual bool equals(Value *value) const { return this->value == ((TValue<int>*)value)->get(); }
    virtual Value *clone() const { return new Int(value); }
};

そして、演算子は次のように解釈されます

Value *v1, *v2,
case OP_PLUS:
{
  if (vm->popTwo(&v1, &v2))
  {  
    switch (v1->type << 4 | v2->type)
    {
      case TYPES(TYPE_INT, TYPE_INT): vm->push(new Int(((Int*)v1)->get() + ((Int*)v2)->get())); break;
      case TYPES(TYPE_FLOAT, TYPE_INT): vm->push(new Float(((Float*)v1)->get() + ((Int*)v2)->get())); break;
      case TYPES(TYPE_INT, TYPE_FLOAT): vm->push(new Float(((Int*)v1)->get() + ((Float*)v2)->get())); break;
      case TYPES(TYPE_FLOAT, TYPE_FLOAT): vm->push(new Float(((Float*)v1)->get() + ((Float*)v2)->get())); break;
    }
  }
  break;
}

さて、これは機能しますが、かなり不器用に聞こえ、多くの型キャストが必要であり、(私の関数実装と比較して) まったくエレガントではないため、このアプローチは好きではありません。すべてを管理するためのより良い方法を見つけることができるかどうかを確認するために、ブーストライブラリを調べ始めています。それを開始する前に、次のような演算子を定義する別の方法を定義しようとしていました。

template <Opcode T, class X, class A>
class Unary
{
public:
  static A* ptr(X* x)
  {
    cout << "Missing instruction!" << endl;
    return NULL;
  };
};


template <>
class Unary<OP_MINUS, Float, Float>
{
  public:
    static Float *ptr(Float *x) { return new Float(-x->get()); };
}; 

できるように

Float *a = new Float(10);
Float *r = Unary<OP_MINUS, Float, Float>::ptr(f);

これは機能しますが、スタック上で見つかったものと使用されている演算子に応じて適切な関数を呼び出すことができるように、一般的な方法でそれを管理する方法をまだ確認できません。

ブーストは何とか私を助けますか?私が望んでいるのは、タイプセーフであると同時にエレガントなソリューションですが、ブーストには非常に多くの異なるライブラリがあり、何を探すべきかを理解するのが難しいです。欠けている簡単なものがある場合は、それを使用する必要はありません。この種のタスクのために関数型言語をドロップするときに、それほど多くの困難を見つけるとは思いませんでした。

4

1 に答える 1

3

boost::variant、および要素のリストについては、boost::make_recursive_variant(型内の型を参照できるように) が必要です。

関数をapply_visitor多くの型に適用することはできますが、次のような方が簡単に開始できることがわかります (コンパイラで C++11 がサポートされていると仮定します)。

template<typename T, typename Func, typename Types...>
bool TryApplyFuncOn( boost::variant<Types...>& var, Func f ) {
  struct HelperVisitor {
    HelperVisitor( Func f_ ):func(f_) {}
    Func func;
    typedef bool return_type;
    template<typename U>
    return_type operator()( U& unused ) { return false; }
    return_type operator()( T& t ) { f(t); return true; }
  };
  return boost::apply_visitor( HelperVisitor(f), var );
}

これは、関数を適用する型とバリアントを取り、適用するように要求した型がバリアントの型である場合に適用します。一致が見つかった場合は true を返します。

一般的なケースでは、これを「1回のパスで」行うことができます。

したがって、次のようなことができます。

// easy case:
typedef boost::variant<int,double> scalar;
scalar times_two(scalar const& left) {
  scalar retval = left;
  TryApplyFuncOn<int>( retval, []( int& value ){ value*=2; } );
  TryApplyFuncOn<double>( retval, []( double& value ){ value*=2.; } );
  return retval;
}
// tricky case:
scalar multiply(scalar const& left, scalar const& right) {
  scalar retval = left;
  TryApplyFuncOn<int>( retval, [&right]( int& left_value ){
    TryApplyFuncOn<int>( right, [&left_value]( int& right_value ){
      left_value *= right_value;
    });
    TryApplyFuncOn<double>( right, [&left_value]( double& right_value ){
      left_value *= right_value;
    });
  });
  TryApplyFuncOn<double>( retval, [&right]( double& left_value ){
    TryApplyFuncOn<int>( right, [&left_value]( int& right_value ){
      left_value *= right_value;
    });
    TryApplyFuncOn<double>( right, [&left_value]( double& right_value ){
      left_value *= right_value;
    });
  });
  return retval;
}

これはまだ型の昇格を行っていません (したがって int*double は double にはなりません) が、それを止める基本的なものは何もありません。

わかる?

于 2012-12-29T17:35:49.633 に答える