Функции-члены класса, реализуемые чертами [фактически, политиками] - PullRequest
6 голосов
/ 15 апреля 2010

Я не хочу говорить, что не могу понять это, но я не могу понять это. Я гуглил, искал переполнение стека и выглянул пустым.

Абстрактная и, возможно, чрезмерно расплывчатая форма вопроса: как я могу использовать шаблон признаков для создания экземпляров функций-членов? [Обновление: я использовал неправильный термин здесь. Это должны быть «политики», а не «черты». Черты описывают существующие классы. Политики предписывают синтетические классы.] Возник вопрос при модернизации набора оптимизаторов многомерных функций, который я написал более 10 лет назад.

Все оптимизаторы работают, выбирая прямой путь через пространство параметров вдали от текущей лучшей точки («обновление»), затем находя лучшую точку на этой линии («поиск линии»), затем проверяя условие «выполнено» и, если не выполнено, итерация.

Существуют разные методы для обновления, поиска строк и, возможно, для проделанного теста и других вещей. Смешивать и сочетать. Различные формулы обновления требуют разных данных о состоянии. Например, обновление LMQN требует вектора, а обновление BFGS требует матрицы. Если оценка градиентов стоит дешево, то поиск строки должен сделать это. Если нет, он должен использовать только оценки функций. Некоторые методы требуют более точного поиска строк, чем другие. Это всего лишь несколько примеров.

Исходная версия создает несколько комбинаций с помощью виртуальных функций. Некоторые характеристики выбираются путем установки битов режима, которые тестируются во время выполнения. Тьфу. Было бы тривиально определить черты с помощью # define и функций-членов с помощью # ifdef и макросов. Но это так двадцать лет назад. Меня беспокоит, что я не могу придумать современный способ.

Если бы существовала только одна изменяющаяся черта, я мог бы использовать любопытно повторяющийся шаблонный шаблон . Но я не вижу способа распространить это на произвольные комбинации черт.

Я пытался сделать это, используя boost::enable_if и т. Д. Специализированная информация о состоянии была простой. Мне удалось выполнить функции, но только благодаря использованию внешних функций, не являющихся друзьями, которые имеют указатель this в качестве параметра. Я даже не придумал, как сделать функции друзьями, а тем более функциями-членами. Компилятор (VC ++ 2008) всегда жаловался, что вещи не совпадают. Я бы крикнул: "СФИНА, ты, придурок!" но придурок, наверное, я.

Возможно, тэг-диспетчер является ключом. Я не очень углубился в это.

Конечно, это возможно, верно? Если так, что является лучшей практикой?

ОБНОВЛЕНИЕ: Вот еще одна попытка объяснить это. Я хочу, чтобы пользователь мог заполнить заказ (манифест) для пользовательского оптимизатора, что-то вроде заказа из китайского меню - один из столбца A, один из столбца B и т. Д. Официант, из столбца A (средства обновления) Я получу обновление BFGS с соусом Холески-декомпозитон. Из столбца B (искатели строк) у меня будет кубический интерполяционный поиск строк с eta 0,4 и относительным значением 1e-4, пожалуйста. Etc ...

ОБНОВЛЕНИЕ: Хорошо, хорошо. Вот игра, которую я сделал. Я неохотно предлагаю это, потому что подозреваю, что это совершенно ошибочный подход. Работает нормально под 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;
}

Ответы [ 5 ]

2 голосов
/ 15 апреля 2010

Простым решением может быть просто использование пересылки на основе тегов, например, что-то как это:

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);
    }
    // ...
};
1 голос
/ 15 апреля 2010

Было бы тривиально определить черты с помощью # define и функций-членов с помощью # ifdef и макросов. Но это так двадцать лет назад.

Хотя, возможно, стоит изучить новые методы, макросы часто являются самым простым способом сделать что-то и их не следует отбрасывать как инструмент только потому, что они «старые». Если вы посмотрите на MPL в ускорении и книгу по TMP, вы найдете много использования препроцессора.

1 голос
/ 15 апреля 2010

Я думаю, что специализация на шаблонах - это шаг в правильном направлении. Это не работает с функциями, поэтому я переключился на классы. Я изменил его, чтобы он изменил данные. Я не так продан на защищенных членах и подружился. Защищенные члены без наследства - это запах. Сделайте его общедоступным или предоставьте средства доступа и сделайте его частным.

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;
};
0 голосов
/ 16 апреля 2010

Вот что я (ОП) придумал. Вы можете сделать это круче?

Основной класс шаблона оптимизатора наследует классы реализации политики. Он предоставляет этим классам доступ к защищенным членам Оптимизатора, которые им необходимы. Другой класс шаблона оптимизатора разбивает манифест на составные части и создает основной шаблон оптимизатора.

#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;
}
0 голосов
/ 15 апреля 2010

Ваше использование 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, что в большинстве случаев подходит в качестве возвращаемого типа. Синтаксис «параметра» обычно зарезервирован для случаев конструктора, потому что тогда в вашем распоряжении нет возвращаемого типа, но в целом вы предпочитаете использовать возвращаемый тип, чтобы он не вмешивался в разрешение перегрузки.

EDIT

Предыдущее решение не работает, как отмечено в комментариях. Я не смог найти альтернативы, используя 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 внутренности всем. ПОЦЕЛУЙ?

...