6

これを理解できないとは言いたくないのですが、これを理解することはできません。Stack Overflow でググって検索したところ、何も表示されませんでした。

質問の抽象的で、おそらく非常に曖昧な形式は、メンバー関数をインスタンス化するために特性パターンを使用するにはどうすればよいですか? です。 [更新: ここで間違った用語を使用しました。「特性」ではなく「ポリシー」であるべきです。特性は既存のクラスを記述します。ポリシーは合成クラスを規定します。]この疑問は、私が 10 年以上前に書いた一連の多変量関数オプティマイザーを最新化するときに出てきました。

オプティマイザーはすべて、現在の最適なポイントから離れたパラメーター空間を通る直線パスを選択し (「更新」)、その線上でより適切なポイントを見つけ (「ライン検索」)、「完了」をテストすることによって動作します。 "条件、および実行されていない場合は繰り返します。

更新、行検索、およびおそらく完了したテストなどを行うには、さまざまな方法があります。ミックス&マッチ。異なる更新式には、異なる状態変数データが​​必要です。たとえば、LMQN の更新にはベクトルが必要であり、BFGS の更新には行列が必要です。勾配の評価が安価である場合は、ライン検索でそうする必要があります。そうでない場合は、関数評価のみを使用する必要があります。一部の方法では、他の方法よりも正確なライン検索が必要です。これらはほんの一例です。

元のバージョンでは、仮想関数を使用していくつかの組み合わせをインスタンス化します。一部の特性は、実行時にテストされるモード ビットを設定することによって選択されます。うん。#define で特性を定義し、#ifdef とマクロでメンバー関数を定義するのは簡単です。しかし、それは20年前のことです。奇抜な現代的な方法を理解できないのは私を悩ませています。

変化する特徴が 1 つだけある場合は、奇妙に繰り返されるテンプレート パターンを使用できます。しかし、それを特性の任意の組み合わせに拡張する方法はありません。

などを使ってやってみboost::enable_ifました。特化した状態情報は簡単でした。this関数を実行することはできましたが、パラメーターとして -pointerを持つフレンド以外の外部関数に頼るしかありませんでした。関数を友達にする方法、ましてやメンバー関数を作る方法さえ理解できませんでした。コンパイラ (VC++ 2008) は、常に一致しないと不平を言っていました。私は「スフィナエ、馬鹿野郎!」と叫びました。しかし、バカはおそらく私です。

おそらくタグディスパッチが鍵です。私はそのことにあまり深く入っていません。

きっと可能ですよね?もしそうなら、ベストプラクティスは何ですか?

更新:これを説明する別の試みがあります。ユーザーがカスタム オプティマイザーの注文 (マニフェスト) に記入できるようにしたいのですが、中国語のメニューから注文するようなものです - 列 A から 1 つ、列 B から 1 つなど..ウェイター、列 A から (アップデーター) 、コレスキー分解ソースで BFGS を更新します。列 B (line-searchers) から、eta が 0.4、rho が 1e-4 の 3 次補間ライン検索を行います。等...

更新: わかりました。これが私が行った遊びです。しぶしぶ提案するのは、完全に間違ったアプローチだと思うからです。vc++ 2008 では問題なく動作します。

#include <boost/utility.hpp>
#include <boost/type_traits/integral_constant.hpp>

namespace dj {

struct CBFGS {
    void bar() {printf("CBFGS::bar %d\n", data);}
    CBFGS(): data(1234){}
    int data;
};

template<class T>
struct is_CBFGS: boost::false_type{};

template<>
struct is_CBFGS<CBFGS>: boost::true_type{};

struct LMQN {LMQN(): data(54.321){}
    void bar() {printf("LMQN::bar %lf\n", data);}
    double data;
};

template<class T>
struct is_LMQN: boost::false_type{};

template<>
struct is_LMQN<LMQN> : boost::true_type{};

// "Order form"
struct default_optimizer_traits {
    typedef CBFGS update_type; // Selection from column A - updaters
};

template<class traits> class Optimizer;

template<class traits>
void foo(typename boost::enable_if<is_LMQN<typename traits::update_type>, 
         Optimizer<traits> >::type& self) 
{
    printf(" LMQN %lf\n", self.data);
}

template<class traits>
void foo(typename boost::enable_if<is_CBFGS<typename traits::update_type>,  
         Optimizer<traits> >::type& self) 
{
    printf("CBFGS %d\n", self.data);
}

template<class traits = default_optimizer_traits>
class Optimizer{
    friend typename traits::update_type;
    //friend void dj::foo<traits>(typename Optimizer<traits> & self); // How?
public:
    //void foo(void); // How???
    void foo() {
        dj::foo<traits>(*this);
    }
    void bar() {
        data.bar();
    }
//protected: // How?
    typedef typename traits::update_type update_type;
    update_type data;
};

} // namespace dj



int main() {
    dj::Optimizer<> opt;
    opt.foo();
    opt.bar();
    std::getchar();
    return 0;
}
4

5 に答える 5

2

簡単な解決策は、タグベースの転送を使用することです。たとえば、のようになります。

template<class traits>
void foo(Optimizer<traits>& self, const LMQN&) {
    printf(" LMQN %lf\n", self.data.data);
}

template<class traits>
void foo(Optimizer<traits>& self, const CBFGS&) {
    printf("CBFGS %d\n", self.data.data);
}

template<class traits = default_optimizer_traits>
class Optimizer {
    friend class traits::update_type;
    friend void dj::foo<traits>(Optimizer<traits>& self, 
                            const typename traits::update_type&);
public:
    void foo() {
        dj::foo<traits>(*this, typename traits::update_type());
    }
    void bar() {
        data.bar();
    }
protected:
    typedef typename traits::update_type update_type;
    update_type data;
};

または、異なる特性のためにいくつかの関数を便利にグループ化したい場合は、おそらくのようになります。

template<class traits, class updater=typename traits::update_type> 
struct OptimizerImpl;

template<class traits>
struct OptimizerImpl<traits, LMQN> {
    static void foo(Optimizer<traits>& self) {
        printf(" LMQN %lf\n", self.data.data);
    }
};

template<class traits> 
struct OptimizerImpl<traits, CBFGS> {
    static void foo(Optimizer<traits>& self) {
        printf("CBFGS %d\n", self.data.data);
    }
};

template<class traits = default_optimizer_traits>
class Optimizer{
    friend class traits::update_type;
    friend struct OptimizerImpl<traits>;
public:
    void foo() {
        OptimizerImpl<traits>::foo(*this);
    }
    // ...
};
于 2010-04-15T12:17:54.770 に答える
1

#define で特性を定義し、#ifdef とマクロでメンバー関数を定義するのは簡単です。しかし、それは20年前のことです。

新しい方法を学ぶ価値はあるかもしれませんが、マクロは多くの場合、物事を行うための最も簡単な方法であり、「古い」という理由だけでツールとして捨てるべきではありません。ブーストの MPL と TMP に関する本を見る​​と、プリプロセッサの多くの使用法が見つかります。

于 2010-04-15T16:25:41.703 に答える
1

テンプレートの専門化は正しい方向への一歩だと思います。これは関数では機能しないため、クラスに切り替えました。データを変更するように変更しました。私は、保護されたメンバーと友達を作ることにはあまり興味がありません。継承のない保護されたメンバーは臭いです。パブリックにするか、アクセサーを提供してプライベートにします。

template <typename>
struct foo;

template <>
struct foo<LMQN>
{
    template <typename OptimizerType>
    void func(OptimizerType& that)
    {
        printf(" LMQN %lf\n", that.data.data);
        that.data.data = 3.14;
    }
};

template <>
struct foo<CBFGS>
{
    template <typename OptimizerType>
    void func(OptimizerType& that)
    {
        printf(" CBFGS %lf\n", that.data.data);
    }
};

template<class traits = default_optimizer_traits>
class Optimizer{
public:
    typedef typename traits::update_type update_type;
    void foo() {
        dj::foo<typename traits::update_type>().func(*this);
    }
    void bar() {
        data.bar();
    }
    update_type data;
};
于 2010-04-15T02:15:01.807 に答える
0

これが私(OP)が思いついたものです。涼しげにできますか?

メインの Optimizer テンプレート クラスは、ポリシー実装クラスを継承します。これらのクラスは、必要なオプティマイザーの保護されたメンバーにアクセスできます。別の Optimizer テンプレート クラスは、マニフェストを構成要素に分割し、メインの Optimizer テンプレートをインスタンス化します。

#include <iostream>
#include <cstdio>

using std::cout;
using std::endl;

namespace dj {

// An updater.
struct CBFGS {
    CBFGS(int &protect_)
        : protect(protect_)
    {}

    void update () {
        cout << "CBFGS " << protect << endl;
    }   

    // Peek at optimizer's protected data
    int &protect;

};

// Another updater
struct LMQN {
    LMQN(int &protect_)
        : protect(protect_)
    {}

    void update () {
        cout << "LMQN " << protect << endl;
    }

    // Peek at optimizer's protected data
    int &protect;

};

// A line-searcher
struct cubic_line_search {
    cubic_line_search (int &protect2_)
        : protect2(protect2_)
    {}

    void line_search() {
        cout << "cubic_line_search  " << protect2 << endl;
    }   

    // Peek at optimizer's protected data
    int &protect2;

};

struct default_search_policies {
    typedef CBFGS update_type;
    typedef cubic_line_search line_search_type;
};

template<class Update, class LineSearch>
class Opt_base: Update, LineSearch
{
public:
    Opt_base()
        : protect(987654321) 
        , protect2(123456789)
        , Update(protect)
        , LineSearch(protect2)
    {}
    void minimize() {
        update();
        line_search();
    }

protected:
    int protect;
    int protect2;
};

template<class Search_Policies=default_search_policies>
class Optimizer: 
    public Opt_base<typename Search_Policies::update_type
                  , typename Search_Policies::line_search_type
                    >
{};

} // namespace dj



int main() {
    dj::Optimizer<> opt; // Use default search policies
    opt.minimize();

    struct my_search_policies {
        typedef dj::LMQN update_type;
        typedef dj::cubic_line_search line_search_type;
    };

    dj::Optimizer<my_search_policies> opt2;
    opt2.minimize();

    std::getchar();
    return 0;
}
于 2010-04-16T18:11:32.793 に答える
0

あなたの の使い方enable_ifは少し奇妙です。私はそれが2つの方法でのみ使用されているのを見てきました:

  • 戻り型の代わりに
  • 補助パラメータとして (デフォルト)

実際のパラメーターに使用すると、混乱が生じる可能性があります。

とにかく、メンバー関数に使用することは間違いなく可能です:

template<class traits = default_optimizer_traits>
class Optimizer{
  typedef typename traits::update_type update_type;
public:

  typename boost::enable_if< is_LQMN<update_type> >::type
  foo()
  {
    // printf is unsafe, prefer the C++ way ;)
    std::cout << "LQMN: " << data << std::endl;
  }

  typename boost::enable_if< is_CBFGS<update_type> >::type
  foo()
  {
    std::cout << "CBFGS: " << data << std::endl;
  }


private:
  update_type data;
};

enable_ifほとんどの場合、これは戻りvoid値の型として非常に適しています。「パラメーター」構文は通常、コンストラクターの場合に予約されています。これは、自由に使用できる戻り値の型がないためですが、一般的には、オーバーロードの解決に干渉しないように戻り値の型を使用することを好みます。

編集

コメントに記載されているように、以前のソリューションは機能しません。enable_if「単純な」オーバーロード方法のみを使用して、代替手段を見つけることができませんでした:

namespace detail
{
  void foo_impl(const LMQN& data)
  {
    std::cout << "LMQN: " << data.data << std::endl;
  }

  void foo_impl(const CBFGS& data)
  {
    std::cout << "CBFGS: " << data.data << std::endl;
  }
} // namespace detail

template<class traits = default_optimizer_traits>
class Optimizer{
  typedef typename traits::update_type update_type;

public:
  void foo() { detail::foo_impl(data); }

private:
  update_type data;
};

そうではありませんが、内部をすべての人enable_ifに公開することなく仕事をします。Optimizer接吻 ?

于 2010-04-15T06:25:42.237 に答える