我不愿意说我无法解决这个问题,但我无法弄清楚这一点。我用Google搜索并搜索了Stack Overflow,然后空了。
问题的抽象,可能过于模糊的形式是, 如何使用traits-pattern来实例化成员函数? [更新:I在这里使用了错误的术语。它应该是“政策”而不是“特征”。特征描述现有的类。政策规定了合成类。] 在对10多年前我写的一组多变量函数优化器进行现代化改造时出现了这个问题。
优化器都通过选择远离当前最佳点(“更新”)的参数空间的直线路径来操作,然后在该线上找到更好的点(“线搜索”),然后测试“完成”条件,如果没有完成,则迭代。
有不同的方法可以进行更新,行搜索,以及完成测试和其他事情。连连看。不同的更新公式需要不同的状态变量数据。例如,LMQN更新需要向量,BFGS更新需要矩阵。如果评估渐变很便宜,则线搜索应该这样做。如果没有,它应该只使用功能评估。某些方法需要比其他方法更精确的线搜索。这只是一些例子。
原始版本通过虚函数实例化几种组合。通过设置在运行时测试的模式位来选择某些特征。呸。使用#define定义特征并使用#ifdef和宏定义成员函数将是微不足道的。但那是二十年前的事了。让我感到困惑的是,我无法想出一种神奇的现代方式。
如果只有一个特性变化,我可以使用奇怪的重复模板模式。但是我认为无法将其扩展到任意的特征组合。
我尝试使用boost::enable_if
等进行此操作。专门的状态信息很简单。我设法完成了这些功能,但只使用了以this
指针作为参数的非朋友外部函数。我甚至从未弄清楚如何使功能成为朋友,更不用说成员功能了。编译器(VC ++ 2008)总是抱怨事情不匹配。我会喊道,“SFINAE,你这个白痴!”但是白痴可能是我。
也许tag-dispatch是关键。我没有深入研究过这个问题。
当然有可能,对吗?如果是这样,最佳做法是什么?
更新:这是另一个解释它的尝试。我希望用户能够为自定义优化器填写订单(清单),例如从中文菜单中排序 - 一个来自A列,一个来自B列等。服务员,来自A列(更新者) ,我将使用Cholesky-decompositon酱更新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;
}
答案 0 :(得分: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);
}
// ...
};
答案 1 :(得分: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;
};
答案 2 :(得分:1)
使用#define定义特征并使用#ifdef和宏定义成员函数将是微不足道的。但那是二十年前的事了。
尽管学习新方法可能是值得的,但宏通常是最简单的做事方式,不应仅仅因为它们“旧”而被丢弃作为工具。如果你看一下boost中的MPL和关于TMP的书,你会发现预处理器的用处很多。
答案 3 :(得分:0)
您使用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
,这在大多数情况下非常适合作为返回类型。 “参数”语法通常是为构造函数的情况保留的,因为您没有可用的返回类型,但通常更喜欢使用返回类型,以便它不会干扰重载解析。
修改强>:
如评论中所述,之前的解决方案不起作用。我找不到使用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
内部的情况下完成工作。吻?
答案 4 :(得分: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;
}