![]() |
Home | Libraries | People | FAQ | More |
Proto v4 is merged to Boost trunk with more powerful
transform protocol.
Proto v4 合併至 Boost 主幹,具有更強大的變換協議。
Proto is accepted into Boost.
Proto 被接受進入 Boost。
Proto's Boost review begins.
Proto 的 Boost 評審開始。
Boost.Proto v3 brings separation of grammars and
transforms and a "round" lambda syntax for defining transforms
in-place.
Boost.Proto v3 將語法和變換分離,並帶有一個就地定義變換的"round" lambda 語法。
Boost.Xpressive is ported from Proto compilers to Proto
transforms. Support for old Proto compilers is dropped.
Boost.Xpressive 從 Proto 編譯器修改為 Proto 變換。不再支持舊的 Proto 編譯器。
Preliminary submission of Proto to Boost.
給 Boost 的 Proto 初步提交物。
The idea for transforms that decorate grammar rules is
born in a private email discussion with Joel de Guzman and Hartmut
Kaiser. The first transforms are committed to CVS 5 days later on
December 16.
在 Joel de Guzman 和 Hartmut Kaiser 的一封私人郵件討論中,誕生了用變換來裝飾語法規則的想法。5天後,12月16日,第一個變換被提交到CVS上。
The idea for proto::matches<>
and the whole grammar facility is hatched during a discussion with
Hartmut Kaiser on the spirit-devel list. The first version of proto::matches<>
is checked into CVS 3 days later. Message is here.
Hartmut Kaiser 在 spirit-devel 列表上的討論中,提出了 proto::matches<>
和整個語法工具的想法。3天後,proto::matches<>
的第一個版本簽入至CVS。相關信息在 此處。
Proto is reborn, this time with a uniform expression
types that are POD. Announcement is here.
Proto 重生,這次將表達式類型為統一為POD。相關聲明在 此處。
Proto is born as a major refactorization of
Boost.Xpressive's meta-programming. Proto offers expression types,
operator overloads and "compilers", an early formulation of what later
became transforms. Announcement is here.
Proto 作為
Boost.Xpressive 元編程的主要重構物誕生了。Proto 提供了表達式類型、操作符重載和"編譯器"--變換的早期形式。相關聲明在 此處。
Proto expression types are PODs (Plain Old Data), and do not
have constructors. They are brace-initialized, as follows:
Proto 表達式類型是POD(舊的平面數據),不帶構造函數。它們可以用花括號來初始化,如下:
terminal<int>::type const _i = {1};
The reason is so that expression objects like _i
above can be statically
initialized. Why is static initialization
important? The terminals of many domain- specific embedded languages
are likely to be global const objects, like _1
and _2 from the
Boost Lambda Library. Were these object to require run-time
initialization, it might be possible to use these objects before they
are initialized. That would be bad. Statically initialized objects
cannot be misused that way.
其原因是,這樣象上面的 _i
這樣的表達式對象就可以被靜態初始化。為什麼靜態初始化這麼重要?許多領域專用嵌入式語言的終結符很可能是全局的常量對象,如 Bosot Lambda 庫中的 _1 和 _2。如果這些對像要在運行期初始化的話,對象就可能會在初始化之前被使用。這是有問題的。靜態初始化的對象不會發生這種問題。
Anyone who has peeked at Proto's source code has probably
wondered, "Why all the dirty preprocessor gunk? Couldn't this have been
all implemented cleanly on top of libraries like MPL and Fusion?" The
answer is that Proto could have been implemented this way, and in fact
was at one point. The problem is that template metaprogramming (TMP)
makes for longer compile times. As a foundation upon which other
TMP-heavy libraries will be built, Proto itself should be as
lightweight as possible. That is achieved by prefering preprocessor
metaprogramming to template metaprogramming. Expanding a macro is far
more efficient than instantiating a template. In some cases, the
"clean" version takes 10x longer to compile than the "dirty" version.
任
何一個看過Proto源代碼的人都可能會覺得奇怪,"為什麼到處是難看的預處理器語句?不能使用象MPL和Fusion這樣的庫來實現得更漂亮嗎?"答案
是,Proto可以用這些方法來實現,事實上也曾經如此。問題是模板元編程(TMP)需要較長的編譯時間。作為其它重度使用TMP的庫的構建基礎,
Proto本身應該盡可能輕量。這需要通過預處理器元編程而不是模板元編程來實現。展開一個宏要比實例化一個模板高效得多。在某些情形下,"漂亮"版要比
"難看"版多花10倍以上的編譯時間。
The "clean and slow" version of Proto can still be found at
http://svn.boost.org/svn/boost/branches/proto/v3. Anyone who is
interested can download it and verify that it is, in fact, unusably
slow to compile. Note that this branch's development was abandoned, and
it does not conform exactly with Proto's current interface.
"
漂亮且慢"的Proto版本仍然可以在 http://svn.boost.org/svn/boost/branches/proto/v3
中找到。有興趣的讀者可以下載它,並且看看它在編譯時是否不一般地慢。注意,這一分支的開發已經不再繼續,它並不完全符合當前的Proto接口。
Much has already been written about dispatching on type
traits using SFINAE (Substitution Failure Is Not An Error) techniques
in C++. There is a Boost library, Boost.Enable_if, to make the
technique idiomatic. Proto dispatches on type traits extensively, but
it doesn't use enable_if<>
very often. Rather, it dispatches based on the presence or absence of
nested types, often typedefs for void.
很多人都寫過使用C++中的SFINAE(替換失敗並非錯誤)技術按類型traits進行分派的代碼。有一個Boost庫,Boost.Enable_if,使這一技術成為慣用法。Proto大量地使用了按類型traits進行分派,但是它並不經常使用 enable_if<>。相以,它基於嵌套類型的存在與否來進行分派,這個嵌套類型通常被 typedef 為 void。
Consider the implementation of is_expr<>.
It could have been written as something like this:
想一下 is_expr<> 的實現。它可以寫成象以下這樣:
template<typename T>
struct is_expr
: is_base_and_derived<proto::some_expr_base, T>
{};
Rather, it is implemented as this:
更好的方法是實現為這樣:
template<typename T, typename Void = void>
struct is_expr
: mpl::false_
{};
template<typename T>
struct is_expr<T, typename T::proto_is_expr_>
: mpl::true_
{};
This relies on the fact that the specialization will be
preferred if T has a nested proto_is_expr_
that is a typedef for void. All Proto
expression types have such a nested typedef.
這依賴於一個事實,即如果 T 帶有一個嵌套的 proto_is_expr_,它可以 typedef 為 void,則會優選模板的特化版。所有Proto表達式類型都帶有這樣的一個嵌套typedef。
Why does Proto do it this way? The reason is because, after
running extensive benchmarks while trying to improve compile times, I
have found that this approach compiles faster. It requires exactly one
template instantiation. The other approach requires at least 2: is_expr<>
and is_base_and_derived<>,
plus whatever templates is_base_and_derived<>
may instantiate.
為什麼Proto要以這種方法來實現呢?原因是,在進行了旨在改進編譯時間的性能測試後,我發現這種方式的編譯更快。它只需要一個模板實例化。另外一種方法則至少需要2個:is_expr<> 和 is_base_and_derived<>,而且模板 is_base_and_derived<>
還可能進行其它的實例化。
In several places, Proto needs to know whether or not a
function object Fun can be
called with certain parameters and take a fallback action if not. This
happens in proto::callable_context<>
and in the proto::call<>
transform. How does Proto know? It involves some tricky
metaprogramming. Here's how.
Proto的多處地方需要知道一個函數對像 Fun 是否可以用指定的參數來調用,如果不行的話則需要進行回退。這在 proto::callable_context<>
和 proto::call<>
變換中都有發生。Proto是如何知道的呢?這裡有一些巧妙的元編程。下面來說一下。
Another way of framing the question is by trying to implement
the following can_be_called<>
Boolean metafunction, which checks to see if a function object Fun
can be called with parameters of type A and B:
構思這一問題的另一個方法是,嘗試實現以下 can_be_called<>
布爾元函數,它檢查某個函數對像 Fun
是否可以用類型為 A 和 B 的參數來調用:
template<typename Fun, typename A, typename B>
struct can_be_called;
First, we define the following dont_care
struct, which has an implicit conversion from anything. And not just
any implicit conversion; it has a ellipsis conversion, which is the
worst possible conversion for the purposes of overload resolution:
首先,我們定義以下 dont_care
結構,它帶有一個自任何類型的隱式轉換。而且不僅是任意的隱式轉換;它還有一個帶省略號的轉換,這是在重載解析中最壞情況下的可選轉換:
struct dont_care
{
dont_care(...);
};
We also need some private type known only to us with an
overloaded comma operator (!), and some functions that detect the
presence of this type and return types with different sizes, as
follows:
我們還需要一些東西,只有我們自己知道的帶有一個逗號操作符重載(!)的私有類型,一些函數用於檢測該類型是否出現並返回不同大小的類型,如下:
struct private_type
{
private_type const &operator,(int) const;
};
typedef char yes_type; // sizeof(yes_type) == 1
typedef char (&no_type)[2]; // sizeof(no_type) == 2
template<typename T>
no_type is_private_type(T const &);
yes_type is_private_type(private_type const &);
Next, we implement a binary function object wrapper with a
very strange conversion operator, whose meaning will become clear
later.
接著,我們實現一個二元函數包裝器,它帶有一個非常奇特的轉換操作符,其意義稍後就會弄清楚。
template<typename Fun>
struct funwrap2 : Fun
{
funwrap2();
typedef private_type const &(*pointer_to_function)(dont_care, dont_care);
operator pointer_to_function() const;
};
With all of these bits and pieces, we can implement can_be_called<>
as follows:
有了所有這些東西,我們就可以如下實現 can_be_called<> 了:
template<typename Fun, typename A, typename B>
struct can_be_called
{
static funwrap2<Fun> &fun;
static A &a;
static B &b;
static bool const value = (
sizeof(no_type) == sizeof(is_private_type( (fun(a,b), 0) ))
);
typedef mpl::bool_<value> type;
};
The idea is to make it so that fun(a,b) will always
compile by adding our own binary function overload, but doing it in
such a way that we can detect whether our overload was selected or not.
And we rig it so that our overload is selected if there is really no
better option. What follows is a description of how can_be_called<>
works.
其中的思想是,通過增加我們自己的二元函數重載,使得 fun(a,b) 總是可以被編譯,不過,這樣一來我們就可以檢測到我們的重載是否被選中。而我們把它裝配成只有在沒有更好的選擇時,才選中我們的重載。以下說明 can_be_called<> 如何工作。
We wrap Fun in a type
that has an implicit conversion to a pointer to a binary function. An
object fun of class
type can be invoked as fun(a,
b) if it has such a
conversion operator, but since it involves a user-defined conversion
operator, it is less preferred than an overloaded operator(), which requires
no such conversion.
我們將 Fun 包入一個類型,該類型帶有一個到二元函數指針的隱式轉換。如果一個類類型帶有這樣的一個轉換操作符,則這個類類型的對象 fun 就可以用 fun(a,
b) 來調用,不過,由於其中包含了一次用戶自定義的轉換操作符,所以它不如一個重載的 operator() 優先,因為後者不需要轉換。
The function pointer can accept any two arguments by virtue
of the dont_care type.
The conversion sequence for each argument is guaranteed to be the worst
possible conversion sequence: an implicit conversion through an
ellipsis, and a user-defined conversion to dont_care.
In total, it means that funwrap2<Fun>()(a,
b) will always
compile, but it will select our overload only if there really is no
better option.
由於 dont_care 類型,這個函數指針可以接受任意類型的兩個參數。每個參數的轉換序列都可以保證是最壞情況下的可選轉換序列:一個通過省略號進行的隱式轉換,和一個到 dont_care 的用戶自定義轉換。總的來說,這意味著 funwrap2<Fun>()(a,
b) 總是可以被編譯,不過僅當沒有更好的選擇時,它才會選擇我們的重載。
If there is a better option --- for example if Fun
has an overloaded function call operator such as void
operator()(A
a, B
b) --- then fun(a, b) will resolve to
that one instead. The question now is how to detect which function got
picked by overload resolution.
如果有一個更好的選擇 --- 例如,如果 Fun
帶有一個重載的函數調用操作符,如 void
operator()(A
a, B
b) --- 則 fun(a, b) 將被決議為那個選擇。現在的問題是,如何通過重載決議來檢測哪一個函數被選中。
Notice how fun(a,
b) appears in can_be_called<>: (fun(a,
b), 0). Why do we use
the comma operator there? The reason is because we are using this
expression as the argument to a function. If the return type of fun(a, b) is void,
it cannot legally be used as an argument to a function. The comma
operator sidesteps the issue.
留意在 can_be_called<> 中的 fun(a,
b) 是如何出現的:(fun(a,
b), 0)。為什麼我們要用逗號操作符?原因是,我們要把這個表達式作為一個函數的參數來使用。 如果 fun(a, b) 的返回類型為 void,它就不能合法地作為一個函數的參數來使用。逗號操作符迴避了這個問題。
This should also make plain the purpose of the overloaded
comma operator in private_type.
The return type of the pointer to function is private_type.
If overload resolution selects our overload, then the type of (fun(a,
b), 0) is private_type.
Otherwise, it is int. That fact is
used to dispatch to either overload of is_private_type(), which encodes
its answer in the size of its return type.
在 private_type 中重載逗號操作符的目的就很顯而易見了。這個函數指針的返回類型是 private_type。如果重載決議選中了我們的重載,則 (fun(a,
b), 0) 的類型為 private_type。否則,其類型為 int。這一事實用於分派 is_private_type() 的重載,後者將答案寫在返回類型的大小中。
That's how it works with binary functions. Now repeat the
above process for functions up to some predefined function arity, and
you're done.
以上就是如何使用這一方法對二元函數進行判斷。現在,可以重複以上過程直至某個預定義的函數airty為止。
I'd like to thank Joel de Guzman and Hartmut Kaiser for being willing to take a chance on using Proto for their work on Spirit-2 and Karma when Proto was little more than a vision. Their requirements and feedback have been indespensable.
Thanks also to the developers of PETE. I found many good ideas there.