'Freezing'表达式
'Freezing' an expression
我有一个C++表达式,我希望"冻结"。我的意思是我有如下语法:
take x*x with x in container ...
其中...
指示进一步(对此问题无用)语法。但是,如果我尝试编译它,无论我使用什么预处理器翻译来使"take"成为"运算符"(在倒逗号中,因为它在技术上不是运算符,但翻译阶段将其变成一个类,例如,运算符*可用),编译器仍然尝试评估/计算 x*x 来自哪里, (而且,由于它以前没有被声明(因为它在"in"阶段进一步声明),它反而)找不到它并抛出编译错误。
我目前的想法本质上是尝试将表达式放在 lambda 中(并且由于我们可以推断容器的类型,因此我们可以将具有正确类型的x
声明为,例如,[](decltype(*begin(container)) x) { return x*x }
- 因此,当编译器查看此语句时,它是有效的并且不会抛出任何错误),但是,我遇到了实际实现此目标的错误。
因此,我的问题是:有没有办法/最好的方法是什么来"冻结"我表情的x*x
部分?
编辑:为了澄清我的问题,请采取以下措施。假设运算符 - 以理智的方式定义,以便以下尝试实现上述take ...
语法的作用:
MyTakeClass() - x*x - MyWithClass() - x - MyInClass() - container ...
当编译此语句时,编译器将抛出一个错误;x没有声明,所以x*x没有意义(x-MyInClass()等也没有意义)。我试图实现的是找到一种方法来编译上述表达式,使用任何可用的巫毒魔法,而无需事先知道x的类型(或者,事实上,它将被命名为x;它可以被命名为"somestupidvariablename")。
一个基于表达式模板的几乎解决方案(注意:这些不是表达式模板,它们基于表达式模板)。 不幸的是,我无法想出一种不需要您预先声明x
的方法,但我确实想出了一种延迟类型的方法,因此您只需全局声明x
一个,并且可以在同一程序/文件/范围内一遍又一遍地将其用于不同类型的类型。 这是使用魔法的表达式类型,我将其设计得非常灵活,您应该能够随意轻松地添加操作和使用。 它的使用方式与您描述的完全一样,除了 x 的预先声明。
我知道的缺点:它确实需要T*T
、T+T
和T(long)
可编译的。
expression x(0, true); //x will be the 0th parameter. Sorry: required :(
int main() {
std::vector<int> container;
container.push_back(-3);
container.push_back(0);
container.push_back(7);
take x*x with x in container; //here's the magic line
for(unsigned i=0; i<container.size(); ++i)
std::cout << container[i] << ' ';
std::cout << 'n';
std::vector<float> container2;
container2.push_back(-2.3);
container2.push_back(0);
container2.push_back(7.1);
take 1+x with x in container2; //here's the magic line
for(unsigned i=0; i<container2.size(); ++i)
std::cout << container2[i] << ' ';
return 0;
}
这是使它正常工作的类和定义:
class expression {
//addition and constants are unused, and merely shown for extendibility
enum exprtype{parameter_type, constant_type, multiplication_type, addition_type} type;
long long value; //for value types, and parameter number
std::unique_ptr<expression> left; //for unary and binary functions
std::unique_ptr<expression> right; //for binary functions
public:
//constructors
expression(long long val, bool is_variable=false)
:type(is_variable?parameter_type:constant_type), value(val)
{}
expression(const expression& rhs)
: type(rhs.type)
, value(rhs.value)
, left(rhs.left.get() ? std::unique_ptr<expression>(new expression(*rhs.left)) : std::unique_ptr<expression>(NULL))
, right(rhs.right.get() ? std::unique_ptr<expression>(new expression(*rhs.right)) : std::unique_ptr<expression>(NULL))
{}
expression(expression&& rhs)
:type(rhs.type), value(rhs.value), left(std::move(rhs.left)), right(std::move(rhs.right))
{}
//assignment operator
expression& operator=(expression rhs) {
type = rhs.type;
value = rhs.value;
left = std::move(rhs.left);
right = std::move(rhs.right);
return *this;
}
//operators
friend expression operator*(expression lhs, expression rhs) {
expression ret(0);
ret.type = multiplication_type;
ret.left = std::unique_ptr<expression>(new expression(std::move(lhs)));
ret.right = std::unique_ptr<expression>(new expression(std::move(rhs)));
return ret;
}
friend expression operator+(expression lhs, expression rhs) {
expression ret(0);
ret.type = addition_type;
ret.left = std::unique_ptr<expression>(new expression(std::move(lhs)));
ret.right = std::unique_ptr<expression>(new expression(std::move(rhs)));
return ret;
}
//skip the parameter list, don't care. Ignore it entirely
expression& operator<<(const expression&) {return *this;}
expression& operator,(const expression&) {return *this;}
template<class container>
void operator>>(container& rhs) {
for(auto it=rhs.begin(); it!=rhs.end(); ++it)
*it = execute(*it);
}
private:
//execution
template<class T>
T execute(const T& p0) {
switch(type) {
case parameter_type :
switch(value) {
case 0: return p0; //only one variable
default: throw std::runtime_error("Invalid parameter ID");
}
case constant_type:
return ((T)(value));
case multiplication_type:
return left->execute(p0) * right->execute(p0);
case addition_type:
return left->execute(p0) + right->execute(p0);
default:
throw std::runtime_error("Invalid expression type");
}
}
//This is also unused, and merely shown as extrapolation
template<class T>
T execute(const T& p0, const T& p1) {
switch(type) {
case parameter_type :
switch(value) {
case 0: return p0;
case 1: return p1; //this version has two variables
default: throw std::runtime_error("Invalid parameter ID");
}
case constant_type:
return value;
case multiplication_type:
return left->execute(p0, p1) * right->execute(p0, p1);
case addition_type:
return left->execute(p0, p1) + right->execute(p0, p1);
default:
throw std::runtime_error("Invalid expression type");
}
}
};
#define take
#define with <<
#define in >>
在 http://ideone.com/Dnb50 以正确的输出进行编译和运行
您可能会注意到,由于必须预先声明x
,因此完全忽略with
部分。 这里几乎没有宏魔法,宏有效地将其变成"x*x >> x << container
",>>x
完全不做任何事情。 所以表达实际上是"x*x << container
"。
另请注意,此方法很慢,因为这是一个解释器,几乎所有的减速都意味着。 但是,它的好处是它是可序列化的,您可以将函数保存到文件中,稍后加载它,然后执行它。
R.MartinhoFernandes观察到,x
的定义可以简化为仅仅是expression x;
,并且可以从with
部分推导出参数的顺序,但它需要对设计进行大量重新思考,并且会更加复杂。 我可能会稍后回来添加该功能,但与此同时,要知道这绝对是可能的。
如果您可以将表达式修改为"take(x*x with x in container)",那么这将消除预先声明"x"的需要,其内容比表达式模板简单得多。 #define , #define 中 , #define take(expr, var, con) \ std::transform(con.begin(), con.end(), con.begin(), \ [](const typename con::value_type& var) -> typename con::value_type \ {return expr;});
int main() {
std::vector<int> container;
container.push_back(-3);
container.push_back(0);
container.push_back(7);
take(x*x with x in container); //here's the magic line
for(unsigned i=0; i<container.size(); ++i)
std::cout << container[i] << ' ';
}
我做了一个与我之前的答案非常相似的答案,但使用实际的表达式模板,这应该要快得多。 不幸的是,MSVC10 在尝试编译时崩溃,但 MSVC11、GCC 4.7.0 和 Clang 3.2 都可以编译并运行它。 (所有其他版本未经测试)
以下是模板的用法。 实现代码在这里。
#define take
#define with ,
#define in >>=
//function call for containers
template<class lhsexpr, class container>
lhsexpr operator>>=(lhsexpr lhs, container& rhs)
{
for(auto it=rhs.begin(); it!=rhs.end(); ++it)
*it = lhs(*it);
return lhs;
}
int main() {
std::vector<int> container0;
container0.push_back(-4);
container0.push_back(0);
container0.push_back(3);
take x*x with x in container0; //here's the magic line
for(auto it=container0.begin(); it!=container0.end(); ++it)
std::cout << *it << ' ';
std::cout << 'n';
auto a = x+x*x+'a'*x;
auto b = a; //make sure copies work
b in container0;
b in container1;
std::cout << sizeof(b);
return 0;
}
如您所见,这与我以前的代码完全相同,只是现在所有函数都是在编译时决定的,这意味着这将具有与lambda完全相同的速度。 事实上,C++11 lambda 之前有 boost::lambda
个,它适用于非常相似的概念。
这是一个单独的答案,因为代码大不相同,而且更加复杂/令人生畏。 这也是为什么实现不在答案本身中的原因。
使用预处理器获得这种"列表理解"(不完全是,但它正在做同样的事情)ala Haskell。预处理器只是执行简单的搜索并替换为参数的可能性,因此它不能执行任意替换。特别是改变表达部分的顺序是不可能的。
我看不到在不更改顺序的情况下执行此操作的方法,因为您总是需要以某种方式出现在x*x
之前x
来定义此变量。使用 lambda 无济于事,因为您仍然需要在x*x
部分前面x
,即使它只是一个参数。这使得此语法不可行。
有一些方法可以解决这个问题:
使用不同的预处理器。有基于 Lisp-macros 思想的预处理器,它可以成为语法感知的,因此可以将一个语法树任意转换为另一个语法树。一个例子是为OCaml语言开发的Camlp4/Camlp5。有一些非常好的教程关于如何使用它进行任意语法转换。我曾经有一个关于如何使用Camlp4将makefile转换为C代码的解释,但是我再也找不到了。还有一些关于如何做这些事情的其他教程。
稍微更改语法。这种列表理解在语义上只是对Monad用法的语法简化。随着C++11的到来,Monads在C++成为可能。然而,句法糖可能不是。如果你决定将你试图做的事情包装在Monad中,很多事情仍然是可能的,你只需要稍微改变语法。不过,在C++中实现Monads一点也不有趣(尽管我最初期望并非如此)。看看这里一些例子,如何让Monads进入C++。
最好的方法是使用预处理器解析它。我确实相信预处理器可以成为构建EDSL(嵌入式域特定语言)的非常强大的工具,但您必须首先了解预处理器解析事物的局限性。预处理器只能解析出预定义的令牌。因此,语法必须通过在表达式周围放置括号来稍微更改,并且还必须用FREEZE
宏包围它(我刚刚选择了 FREEZE,它可以被称为任何东西):
FREEZE(take(x*x) with(x, container))
使用此语法,您可以将其转换为预处理器序列(当然,使用 Boost.Preprocessor 库)。一旦你把它作为一个预处理器序列,你可以对它应用很多算法来转换它,按照你喜欢的方式。Linq 库 for C++ 也采用了类似的方法,您可以在其中编写以下内容:
LINQ(from(x, numbers) where(x > 2) select(x * x))
现在,要首先转换为 pp 序列,您需要定义要解析的关键字,如下所示:
#define KEYWORD(x) BOOST_PP_CAT(KEYWORD_, x)
#define KEYWORD_take (take)
#define KEYWORD_with (with)
因此,当您调用KEYWORD(take(x*x) with(x, container))
时,它将扩展到 (take)(x*x) with(x, container)
,这是将其转换为 pp 序列的第一步。现在为了继续前进,我们需要使用 Boost.Preprocessor 库中的 while 构造,但首先我们需要定义一些小宏来帮助我们:
// Detects if the first token is parenthesis
#define IS_PAREN(x) IS_PAREN_CHECK(IS_PAREN_PROBE x)
#define IS_PAREN_CHECK(...) IS_PAREN_CHECK_N(__VA_ARGS__,0)
#define IS_PAREN_PROBE(...) ~, 1,
#define IS_PAREN_CHECK_N(x, n, ...) n
// Detect if the parameter is empty, works even if parenthesis are given
#define IS_EMPTY(x) BOOST_PP_CAT(IS_EMPTY_, IS_PAREN(x))(x)
#define IS_EMPTY_0(x) BOOST_PP_IS_EMPTY(x)
#define IS_EMPTY_1(x) 0
// Retrieves the first element of the sequence
// Example:
// HEAD((1)(2)(3)) // Expands to (1)
#define HEAD(x) PICK_HEAD(MARK x)
#define MARK(...) (__VA_ARGS__),
#define PICK_HEAD(...) PICK_HEAD_I(__VA_ARGS__,)
#define PICK_HEAD_I(x, ...) x
// Retrieves the tail of the sequence
// Example:
// TAIL((1)(2)(3)) // Expands to (2)(3)
#define TAIL(x) EAT x
#define EAT(...)
这样可以更好地检测括号和空。它提供了一个HEAD
和TAIL
宏,其工作方式与BOOST_PP_SEQ_HEAD
略有不同。(Boost.Preprocessor 无法处理具有变量参数的序列)。现在,我们如何定义一个使用 while 构造的 TO_SEQ
宏:
#define TO_SEQ(x) TO_SEQ_WHILE_M
(
BOOST_PP_WHILE(TO_SEQ_WHILE_P, TO_SEQ_WHILE_O, (,x))
)
#define TO_SEQ_WHILE_P(r, state) TO_SEQ_P state
#define TO_SEQ_WHILE_O(r, state) TO_SEQ_O state
#define TO_SEQ_WHILE_M(state) TO_SEQ_M state
#define TO_SEQ_P(prev, tail) BOOST_PP_NOT(IS_EMPTY(tail))
#define TO_SEQ_O(prev, tail)
BOOST_PP_IF(IS_PAREN(tail),
TO_SEQ_PAREN,
TO_SEQ_KEYWORD
)(prev, tail)
#define TO_SEQ_PAREN(prev, tail)
(prev (HEAD(tail)), TAIL(tail))
#define TO_SEQ_KEYWORD(prev, tail)
TO_SEQ_REPLACE(prev, KEYWORD(tail))
#define TO_SEQ_REPLACE(prev, tail)
(prev HEAD(tail), TAIL(tail))
#define TO_SEQ_M(prev, tail) prev
现在,当您调用TO_SEQ(take(x*x) with(x, container))
时,您应该得到一个序列(take)((x*x))(with)((x, container))
。
现在,这个序列更容易使用(因为 Boost.Preprocessor 库)。您现在可以反转它、转换它、过滤它、折叠它等。这是非常强大的,并且比将它们定义为宏要灵活得多。例如,在 Linq 库中,查询from(x, numbers) where(x > 2) select(x * x)
转换为以下宏:
LINQ_WHERE(x, numbers)(x > 2) LINQ_SELECT(x, numbers)(x * x)
然后,这些宏将生成用于列表理解的 lambda,但是当它生成 lambda 时,它们还有更多工作要做。同样的事情也可以在您的库中完成,take(x*x) with(x, container)
可以转换为以下内容:
FREEZE_TAKE(x, container, x*x)
另外,您没有定义像take
这样入侵全局空间的宏。
注意:此处的这些宏需要 C99 预处理器,因此在 MSVC 中不起作用。(不过有解决方法)
- (C++)分析树以计算返回错误值的简单算术表达式
- 在VS2010-VS2015下编译时,如何使用decltype作为较大类型表达式的LHS
- 提升精神:解析布尔表达式并简化为规范范式
- 不能在初始值设定项列表中将非常量表达式从类型 'int' 缩小到'unsigned long long'
- 使用正则表达式regex_search在字符串中查找字符串
- 如何确认我的constexpr表达式实际上已经在编译时执行
- 概念中的cv限定符需要表达式参数列表
- 为什么constexpr的性能比正常表达式差
- 对于结构,表达式必须是可修改的ivalue
- 当一个值是非常量但用常量表达式初始化时使用constexpr
- 将fold表达式与std::一起用于两个元组
- 断言中的Fold表达式在某些计算机上编译,但在其他计算机上不编译
- 标记 '","' 之前的预期主表达式
- gcc和clang在表达式是否为常量求值的问题上存在分歧
- 如何计算具有指定类型的表达式的相对精度和绝对精度
- 带有用户定义类的c++折叠表达式
- 即使使用调试编译标志,表达式也是"optimized out"
- holeMenuProgram.cpp:38:1 错误:'}'令牌之前的预期主表达式
- 在 C++ 中使用正则表达式错误时出现问题 括号表达式中的范围无效
- 'Freezing'表达式