【问题标题】:Class member functions instantiated by traits [policies, actually]由特征实例化的类成员函数 [实际上是策略]
【发布时间】:2010-04-14 22:00:33
【问题描述】:

我不愿意说我想不通,但我想不通。我用谷歌搜索并搜索了 Stack Overflow,结果是空的。

这个问题的抽象且可能过于模糊的形式是,如何使用特征模式来实例化成员函数? [更新:我在这里使用了错误的术语。它应该是“政策”而不是“特质”。特征描述现有的类。策略规定了合成类。] 在对我 10 多年前编写的一组多元函数优化器进行现代化改造时出现了这个问题。

优化器都通过选择一条远离当前最佳点的直线路径通过参数空间(“更新”),然后在该线上找到更好的点(“线搜索”),然后测试“完成”条件,如果没有完成,则进行迭代。

有不同的方法来进行更新、行搜索、完成测试和其他事情。连连看。不同的更新公式需要不同的状态变量数据。例如,LMQN 更新需要一个向量,而 BFGS 更新需要一个矩阵。如果评估梯度很便宜,那么线搜索应该这样做。如果没有,它应该只使用函数评估。有些方法比其他方法需要更准确的线搜索。这些只是一些例子。

原始版本通过虚函数实例化了几种组合。通过设置在运行时测试的模式位来选择一些特征。呸。用#define 定义特征并用#ifdef 和宏定义成员函数将是微不足道的。但那是二十年前的事了。让我感到困扰的是,我无法找到一种非常棒的现代方式。

如果只有一个不同的特征,我可以使用奇怪地重复出现的模板模式。但我认为没有办法将其扩展到特征的任意组合。

我尝试使用boost::enable_if 等进行此操作。专门的状态信息很容易。我设法完成了这些功能,但只能求助于以this-pointer 作为参数的非朋友外部函数。我什至从未想过如何让函数成为朋友,更不用说成员函数了。编译器(VC++ 2008)总是抱怨事情不匹配。我会大喊:“SFINAE,你这个白痴!”但这个白痴可能是我。

也许标签调度是关键。我还没有深入了解。

当然有可能,对吧?如果是这样,最佳做法是什么?

更新:这是另一个解释它的尝试。我希望用户能够为自定义优化器填写订单(清单),例如从中文菜单中订购 - 一个来自 A 列,一个来自 B 列,等等。服务员,来自 A 列(更新程序) ,我将使用 Cholesky 分解酱更新 BFGS。请从 B 列(线搜索器)开始,我将进行三次插值线搜索,其 eta 为 0.4,rho 为 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;
}

【问题讨论】:

  • 你能用伪代码举例说明你现在正在做什么吗?
  • @Chris Kaminski:10 年前的代码,还是实验?我想是后者。
  • 也许两者都有?我的意思是,在 STL 和 boost 中有很多关于使用对集合操作的函数的示例,例如 map/reduce。
  • 我对“实例化非虚拟成员函数”感到困惑。
  • “实例化”是编译器在调用模板时对模板执行的操作。编译器为模板所代表的抽象创建一个具体的“实例”。

标签: c++ templates boost traits member-functions


【解决方案1】:

一个简单的解决方案可能是只使用基于标签的转发,例如类似的东西

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

或者,如果您想方便地将多个函数组合在一起以获得不同的特征,也许 something 像这样:

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

【讨论】:

  • 这就是我要写的东西。当手头的问题肯定不需要它时,我一直专注于使用enable_if。一个简单的Strategy 模式或基于Policy 的设计似乎更合适。
  • 我在同一条轨道上。这个想法是在我睡觉时想到的。这经常发生在我身上。我的想法是,因为除非包含类是专门专门化的,否则成员函数不能专门化,我将有一个模板,将清单拆分为其组件部分,然后调用另一个模板,将它们分开,就像你展示的那样。我今天有狗和国税局要安抚。但我会解决的。
  • @Jive:让我们知道进展如何。从您所展示的内容来看,我认为没有必要插入另一个模板层 - 但您可能还有其他想法。
【解决方案2】:

我认为模板专业化是朝着正确方向迈出的一步。这不适用于函数,所以我切换到类。我改变了它,所以它修改了数据。我对受保护的成员和结交朋友并不那么感兴趣。没有继承的受保护成员是一种气味。将其设为公开或提供访问器并将其设为私有。

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

【讨论】:

  • 更新程序 foo 无法更新类 Optimizer 的受保护成员或读取其状态。要实例化的函数应该是优化器类的成员,否则,作为参数传递优化器的 this 指针的朋友。看看我所做的尝试。这些函数可以工作,但前提是我将优化器类中的所有内容都公开,因为我还没有弄清楚如何将函数声明为朋友。
  • (再次评论,因为 Stackoverflow 不允许我编辑前一个。)我认为您在明确专业化方面走在正确的轨道上。但是这个例子没有抓住重点。更新程序 foo 无法更新类 Optimizer 的受保护成员或读取其状态。 [...和以前一样]。
  • 看起来这种方法是死路一条。在这里接受的答案中,stackoverflow.com/questions/2097811/… 它说:“如果不明确专门化包含类,就无法专门化成员函数。”所以我们进入了中文菜单的“多次调度”问题。但有希望。它接着说,“但是,您可以做的是将调用转发到部分专用类型的成员函数。”我会调查的。 (#defines、#ifdef 和宏看起来越来越好。:-))
  • 我看到你修改了代码。现在你到了让我难过的地步。如何将函数声明为朋友?但是你把它包装在一个类中,我可以将该类声明为朋友。所以这很接近。我真的很想让这个函数成为一个合适的成员。但是请看上面的评论。我只想告诉编译器,如果您在特征结构中看到“CBFGS”,请剪切并粘贴成员函数“foo”(或者更好的是“update”)的这段代码。令人沮丧的是,预处理器如此简单的事情却令人难以置信的模板。但我不能放弃一个谜题。
【解决方案3】:

用#define 定义特征,用#ifdef 和宏定义成员函数是很简单的。但那是二十年前的事了。

虽然可能值得学习新方法,但宏通常是最简单的做事方式,不应仅仅因为它们“旧”而将其作为工具丢弃。如果您查看 boost 中的 MPL 和有关 TMP 的书,您会发现预处理器有很多用途。

【讨论】:

  • 我现在只是认为这是一个难题。我绝对是老派。我对预处理器宏没有过敏反应。具有讽刺意味的是,我为一些我正在努力解决的模板功能而竞选。但这没有任何效果。 Bjarne 同意这些功能,但当时他说他已经把它交给了委员会,并且几乎没有参与其中。
【解决方案4】:

这是我(OP)想出的。你能让它更酷吗?

主优化器模板类继承策略实现类。它使这些类可以访问它们需要的优化器的受保护成员。另一个优化器模板类将清单拆分为其组成部分并实例化主优化器模板。

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

【讨论】:

    【解决方案5】:

    您对enable_if 的使用有点奇怪。我看到它只使用了两种方式:

    • 代替返回类型
    • 作为补充参数(默认)

    将其用于实际参数可能会造成严重破坏。

    无论如何,它绝对可以用于成员函数:

    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,这在大多数情况下非常适合作为返回类型。 “参数”语法通常是为构造函数保留的,因为那时你没有返回类型可供使用,但通常更喜欢使用返回类型,这样它就不会干扰重载决议。

    编辑

    前面的解决方案不起作用,如 cmets 中所述。我找不到使用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 内部结构的情况下完成这项工作。亲亲?

    【讨论】:

    • 您确定代码有效吗?从类似的代码中,我得到了真正的错误,而不是替换失败(这不仅仅是函数模板中的错误)。
    • 我想我明白了:我们不是在测试适合方法的模板参数,而是在我们实例化类时固定模板参数,我猜这将对 SFINAE 造成严重破坏。我将不得不尝试想出一个更好的选择。我猜这就是你不编译时会发生的情况。
    • 哈哈,时间已经过去了,伙计!你仍然需要修复它xD
    • 我发现的唯一选择是不使用模板:/无论如何我都会添加它。
    猜你喜欢
    • 2015-06-28
    • 2012-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-02
    相关资源
    最近更新 更多