Функции-члены класса, реализуемые чертами [фактически, политиками]

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

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

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

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

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

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

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

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

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

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

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

Решение

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

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;
};

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

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);
    }
    // ...
};

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

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

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

Основной класс шаблона 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;
}

Ваше использование 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 внутренности всем. Поцелуй?

Другие вопросы по тегам