3

'フリーズ'したいC++式があります。これは、次のような構文があることを意味します。

take x*x with x in container ...

ここで、...はさらに(この問題には役に立たない)構文を示します。ただし、これをコンパイルしようとすると、「take」を「operator」にするために使用したプリプロセッサ変換に関係なく(技術的には演算子ではないため、引用符で囲みますが、変換フェーズでは、次のようなクラスに変換されます) 、operator *を使用できます)、コンパイラはx * xがどこから来ているかを評価/計算しようとします(そして、以前に宣言されていないため('in'段階でさらに宣言されているため)、代わりに)それを見つけることができず、コンパイルエラーをスローします。

私の現在のアイデアは、基本的に、式をラムダ内に配置しようとすることを含みます(そして、コンテナーの型を推測できるのでx、たとえば、正しい型で宣言できます[](decltype(*begin(container)) x) { return x*x }-したがって、コンパイラーがこのステートメントを見ると、それは有効ですエラーはスローされません)、しかし、私は実際にこれを達成するエラーに遭遇しています。

x*xしたがって、私の質問は次のとおりです。私の表現の一部を「フリーズ」する方法/最良の方法は何ですか?

編集:私の質問を明確にするために、次のことを行ってください。take ...演算子-が適切な方法で定義されていると仮定して、次のように上記の構文の機能を実現しようとします。

MyTakeClass() - x*x - MyWithClass() - x - MyInClass() - container ...

このステートメントがコンパイルされると、コンパイラーはエラーをスローします。xは宣言されていないため、x * xは意味がありません(x-MyInClass()なども意味がありません)。私が達成しようとしているのは、xのタイプを知らなくても、利用可能なブードゥーの魔法を使用して、上記の式をコンパイルする方法を見つけることです(または、実際には、xという名前になります。事前にsomestupidvariablename')。

4

4 に答える 4

2

私は、式テンプレートに基づいて、ほぼ解決策を考え出しました(注:これらは式テンプレートではなく、式テンプレートに基づいています)。残念ながら、事前に宣言する必要のない方法は思いつきませんでしたがx型を遅らせる方法を思いついたので、グローバルに宣言するだけで、さまざまな型に何度も使用できます。同じプログラム/ファイル/スコープ内。これが魔法を働かせる表現タイプです。私は非常に柔軟に設計しました。操作を簡単に追加して、自由に使用できるはずです。xの事前宣言を除いて、説明したとおりに使用されます。x

私が知っている欠点:それは、、を必要としT*TT+TコンパイルT(long)可能である必要があります。

expression x(0, true); //x will be the 0th parameter.  Sorry: required :(

int main() {
    std::vector<int> container;
    container.push_back(-3);
    container.push_back(0);
    container.push_back(7);
    take x*x with x in container; //here's the magic line
    for(unsigned i=0; i<container.size(); ++i)
        std::cout << container[i] << ' ';

    std::cout << '\n';
    std::vector<float> container2;
    container2.push_back(-2.3);
    container2.push_back(0);
    container2.push_back(7.1);
    take 1+x with x in container2; //here's the magic line
    for(unsigned i=0; i<container2.size(); ++i)
        std::cout << container2[i] << ' ';

    return 0;
}

これがクラスと定義で、すべてが機能します。

class expression {
    //addition and constants are unused, and merely shown for extendibility
    enum exprtype{parameter_type, constant_type, multiplication_type, addition_type} type;
    long long value; //for value types, and parameter number
    std::unique_ptr<expression> left; //for unary and binary functions
    std::unique_ptr<expression> right; //for binary functions
   
public:
    //constructors
    expression(long long val, bool is_variable=false) 
    :type(is_variable?parameter_type:constant_type), value(val)
    {}
    expression(const expression& rhs) 
    : type(rhs.type)
    , value(rhs.value)
    , left(rhs.left.get() ? std::unique_ptr<expression>(new expression(*rhs.left)) : std::unique_ptr<expression>(NULL))
    , right(rhs.right.get() ? std::unique_ptr<expression>(new expression(*rhs.right)) : std::unique_ptr<expression>(NULL))
    {}
    expression(expression&& rhs) 
    :type(rhs.type), value(rhs.value), left(std::move(rhs.left)), right(std::move(rhs.right)) 
    {}
    //assignment operator
    expression& operator=(expression rhs) {
       type = rhs.type;
       value = rhs.value;
       left = std::move(rhs.left);
       right = std::move(rhs.right);
       return *this;
    } 
 
    //operators
    friend expression operator*(expression lhs, expression rhs) {
        expression ret(0);
        ret.type = multiplication_type;
        ret.left = std::unique_ptr<expression>(new expression(std::move(lhs)));
        ret.right = std::unique_ptr<expression>(new expression(std::move(rhs)));
        return ret;
    }
    friend expression operator+(expression lhs, expression rhs) {
        expression ret(0);
        ret.type = addition_type;
        ret.left = std::unique_ptr<expression>(new expression(std::move(lhs)));
        ret.right = std::unique_ptr<expression>(new expression(std::move(rhs)));
        return ret;
    }
    
    //skip the parameter list, don't care.  Ignore it entirely
    expression& operator<<(const expression&) {return *this;}
    expression& operator,(const expression&) {return *this;}

    template<class container>    
    void operator>>(container& rhs) {
        for(auto it=rhs.begin(); it!=rhs.end(); ++it)
            *it = execute(*it);
    }  
private: 
    //execution
    template<class T>
    T execute(const T& p0) {
       switch(type) {
       case parameter_type :
           switch(value) {
           case 0: return p0; //only one variable
           default: throw std::runtime_error("Invalid parameter ID");
           }
       case constant_type:
           return ((T)(value));
       case multiplication_type:
           return left->execute(p0) * right->execute(p0);
       case addition_type:
           return left->execute(p0) + right->execute(p0);
       default: 
           throw std::runtime_error("Invalid expression type");
       }
    }
    //This is also unused, and merely shown as extrapolation
    template<class T>
    T execute(const T& p0, const T& p1) {
       switch(type) {
       case parameter_type :
           switch(value) {
           case 0: return p0;
           case 1: return p1; //this version has two variables
           default: throw std::runtime_error("Invalid parameter ID");
           }
       case constant_type:
           return value;
       case multiplication_type:
           return left->execute(p0, p1) * right->execute(p0, p1);
       case addition_type:
           return left->execute(p0, p1) + right->execute(p0, p1);
       default: 
           throw std::runtime_error("Invalid expression type");
       }
    }
}; 
#define take 
#define with <<
#define in >>
  

http://ideone.com/Dnb50で正しい出力でコンパイルおよび実行されます

xを事前に宣言する必要があるため、withセクションが完全に無視されていることに気付くかもしれません。ここにはマクロの魔法はほとんどありません。マクロは効果的にそれを" x*x >> x << container"に変換しますが、ここで>>xはまったく何もしません。したがって、式は事実上x*x << container」です。

また、これはインタプリタであるため、この方法は遅いことに注意してください。これは、ほとんどすべての速度低下を意味します。ただし、シリアル化できるという利点があります。関数をファイルに保存し、後でロードしてから実行することができます

R.MartinhoFernandesは、の定義をx単純化して単純にするexpression x;ことができ、セクションからパラメーターの順序を推測できることを確認しましたwithが、設計を何度も再考する必要があり、より複雑になります。後で戻ってその機能を追加するかもしれませんが、それまでの間、それは間違いなく可能であることを知っています。


式を`take(x * x with x in container)`に変更できる場合は、式テンプレートよりもはるかに単純なものを使用して、`x`を事前に宣言する必要がなくなります。#define with、#define in、#define take(expr、var、con)\ std :: transform(con.begin()、con.end()、con.begin()、\ [](const typename con: :value_type&var)-> typename con :: value_type \ {return expr;});
int main() {
    std::vector<int> container;
    container.push_back(-3);
    container.push_back(0);
    container.push_back(7);
    take(x*x with x in container); //here's the magic line
    for(unsigned i=0; i<container.size(); ++i)
        std::cout << container[i] << ' ';
}
于 2012-06-13T20:08:51.620 に答える
1

以前の回答と非常によく似た回答をしましたが、実際の式テンプレートを使用しました。これははるかに高速です。残念ながら、MSVC10はこれをコンパイルしようとするとクラッシュしますが、MSVC11、GCC 4.7.0、およびClang3.2はすべて正常にコンパイルおよび実行されます。(他のすべてのバージョンはテストされていません)

テンプレートの使用法は次のとおりです。実装コードはこちらです。

#define take 
#define with ,
#define in >>= 

//function call for containers 
template<class lhsexpr, class container>
lhsexpr operator>>=(lhsexpr lhs, container& rhs)
{
    for(auto it=rhs.begin(); it!=rhs.end(); ++it)
        *it = lhs(*it);
    return lhs;
}

int main() {
    std::vector<int> container0;
    container0.push_back(-4);
    container0.push_back(0);
    container0.push_back(3);
    take x*x with x in container0; //here's the magic line
    for(auto it=container0.begin(); it!=container0.end(); ++it)
        std::cout << *it << ' ';
    std::cout << '\n';

    auto a = x+x*x+'a'*x;
    auto b = a; //make sure copies work
    b in container0;
    b in container1;
    std::cout << sizeof(b);

    return 0;
}

ご覧のとおり、これは以前のコードとまったく同じように使用されますが、すべての関数がコンパイル時に決定されるため、ラムダとまったく同じ速度になります。実際、C ++ 11ラムダの前には、非常によく似た概念boost::lambdaで動作するラムダがありました。

コードがはるかに異なり、はるかに複雑/威圧的であるため、これは別の答えです。それはまた、実装が答え自体にない理由でもあります。

于 2012-06-15T22:51:23.403 に答える
0

プリプロセッサを使ってこの「リストの理解」を得るのは不可能だと思います(完全ではありませんが、同じことをしています)。プリプロセッサは単純な検索を実行し、引数の可能性で置換するだけなので、任意の置換を実行することはできません。特に表現部分の順番を変えることはできません。

この変数を定義xする前に必ず何らかの方法で表示する必要があるため、順序を変更せずにこれを行う方法を見つけることはできません。ラムダを使用しても、それが単なる引数であっても、パーツの前に配置x*xする必要があるため、役に立ちません。これにより、この構文は不可能になります。xx*x

これを回避する方法はいくつかあります。

  • 別のプリプロセッサを使用してください。Lispマクロのアイデアに基づいたプリプロセッサがあります。これは構文を認識させることができるため、ある構文ツリーを別の構文ツリーに任意に変換できます。一例は、OCaml言語用に開発されたCamlp4/Camlp5です。これを任意の構文変換に使用する方法について、非常に優れたチュートリアルがいくつかあります。Camlp4を使用してmakefileをCコードに変換する方法について説明していたのですが、もう見つかりません。そのようなことを行う方法については、他にもいくつかのチュートリアルがあります。

  • 構文を少し変更します。このようなリスト内包表記は、本質的にはモナドの使用法を構文的に単純化したものにすぎません。C ++ 11の登場により、C++でモナドが可能になりました。ただし、構文糖衣はそうではない場合があります。やろうとしていることをモナドでラップすることにした場合でも、多くのことが可能です。構文を少し変更するだけです。C ++でモナドを実装するのは楽しいことではありません(私は最初はそうではないと思っていましたが)。C++でモナドを取得する方法の例についてはこちらをご覧ください。

于 2012-06-13T08:34:42.910 に答える
0

最善のアプローチは、プリプロセッサを使用して解析することです。プリプロセッサはEDSL(組み込みドメイン固有言語)を構築するための非常に強力なツールになると思いますが、最初にプリプロセッサの解析の制限を理解する必要があります。プリプロセッサは、事前定義されたトークンのみを解析できます。したがって、式の前後に括弧を配置して構文を少し変更する必要があり、FREEZEマクロもそれを囲む必要があります(FREEZEを選択しただけで、何でも呼び出すことができます)。

FREEZE(take(x*x) with(x, container))

この構文を使用すると、プリプロセッサシーケンスに変換できます(もちろん、Boost.Preprocessorライブラリを使用します)。プリプロセッサシーケンスとして取得したら、多くのアルゴリズムを適用して、好きなように変換できます。同様のアプローチは、C ++用のLinqライブラリを使用して実行されます。ここでは、次のように記述できます。

LINQ(from(x, numbers) where(x > 2) select(x * x))

ここで、最初にppシーケンスに変換するには、次のように、解析するキーワードを定義する必要があります。

#define KEYWORD(x) BOOST_PP_CAT(KEYWORD_, x)
#define KEYWORD_take (take)
#define KEYWORD_with (with)

したがって、これが機能する方法は、呼び出すときにKEYWORD(take(x*x) with(x, container))に展開され(take)(x*x) with(x, container)ます。これは、ppシーケンスに変換するための最初のステップです。続行するには、Boost.Preprocessorライブラリからwhileコンストラクトを使用する必要がありますが、最初に、途中で役立ついくつかの小さなマクロを定義する必要があります。

// Detects if the first token is parenthesis
#define IS_PAREN(x) IS_PAREN_CHECK(IS_PAREN_PROBE x)
#define IS_PAREN_CHECK(...) IS_PAREN_CHECK_N(__VA_ARGS__,0)
#define IS_PAREN_PROBE(...) ~, 1,
#define IS_PAREN_CHECK_N(x, n, ...) n
// Detect if the parameter is empty, works even if parenthesis are given
#define IS_EMPTY(x) BOOST_PP_CAT(IS_EMPTY_, IS_PAREN(x))(x)
#define IS_EMPTY_0(x) BOOST_PP_IS_EMPTY(x)
#define IS_EMPTY_1(x) 0 

// Retrieves the first element of the sequence
// Example:
// HEAD((1)(2)(3)) // Expands to (1)
#define HEAD(x) PICK_HEAD(MARK x)
#define MARK(...) (__VA_ARGS__),
#define PICK_HEAD(...) PICK_HEAD_I(__VA_ARGS__,)
#define PICK_HEAD_I(x, ...) x

// Retrieves the tail of the sequence
// Example:
// TAIL((1)(2)(3)) // Expands to (2)(3)
#define TAIL(x) EAT x
#define EAT(...)

これにより、括弧と空の検出が向上します。また、とは少し異なる動作をするマクロをHEAD提供します。(Boost.Preprocessorは、vardiacパラメーターを持つシーケンスを処理できません)。ここで、while構文を使用するマクロを定義する方法を説明します。TAILBOOST_PP_SEQ_HEADTO_SEQ

#define TO_SEQ(x) TO_SEQ_WHILE_M \
( \
BOOST_PP_WHILE(TO_SEQ_WHILE_P, TO_SEQ_WHILE_O, (,x)) \
)

#define TO_SEQ_WHILE_P(r, state) TO_SEQ_P state
#define TO_SEQ_WHILE_O(r, state) TO_SEQ_O state
#define TO_SEQ_WHILE_M(state) TO_SEQ_M state

#define TO_SEQ_P(prev, tail) BOOST_PP_NOT(IS_EMPTY(tail))
#define TO_SEQ_O(prev, tail) \
BOOST_PP_IF(IS_PAREN(tail), \
TO_SEQ_PAREN, \
TO_SEQ_KEYWORD \
)(prev, tail)
#define TO_SEQ_PAREN(prev, tail) \
(prev (HEAD(tail)), TAIL(tail))

#define TO_SEQ_KEYWORD(prev, tail) \
TO_SEQ_REPLACE(prev, KEYWORD(tail))

#define TO_SEQ_REPLACE(prev, tail) \
(prev HEAD(tail), TAIL(tail))

#define TO_SEQ_M(prev, tail) prev

これで、呼び出すときにTO_SEQ(take(x*x) with(x, container))シーケンスを取得する必要があります(take)((x*x))(with)((x, container))

これで、このシーケンスの操作がはるかに簡単になりました(Boost.Preprocessorライブラリがあるため)。これで、反転、変換、フィルタリング、折り畳みなどを行うことができます。これは非常に強力であり、マクロとして定義するよりもはるかに柔軟性があります。たとえば、Linqライブラリでは、クエリfrom(x, numbers) where(x > 2) select(x * x)は次のマクロに変換されます。

LINQ_WHERE(x, numbers)(x > 2) LINQ_SELECT(x, numbers)(x * x)

これらのマクロのどれを使用すると、リスト内包表記のラムダが生成されますが、ラムダを生成するときに使用できる機能はさらに多くなります。ライブラリでも同じことができ、take(x*x) with(x, container)次のように変換できます。

FREEZE_TAKE(x, container, x*x)

takeさらに、グローバル空間に侵入するようなマクロを定義しているわけではありません。

注:ここでのこれらのマクロにはC99プリプロセッサーが必要であるため、MSVCでは機能しません(回避策はあります)

于 2012-06-14T07:25:34.430 に答える