Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

Appendices 附錄

Appendix A: History 附錄A:歷史
Appendix B: Rationale 附錄B:基本原理
Appendix C: Implementation Notes 附錄C:實現說明
Appendix D: Acknowledgements 附錄D:鳴謝

August 11, 2008

Proto v4 is merged to Boost trunk with more powerful transform protocol.
Proto v4 合併至 Boost 主幹,具有更強大的變換協議。

April 7, 2008

Proto is accepted into Boost.
Proto 被接受進入 Boost。

March 1, 2008

Proto's Boost review begins.
Proto 的 Boost 評審開始。

January 11, 2008

Boost.Proto v3 brings separation of grammars and transforms and a "round" lambda syntax for defining transforms in-place.
Boost.Proto v3 將語法和變換分離,並帶有一個就地定義變換的"round" lambda 語法。

April 15, 2007

Boost.Xpressive is ported from Proto compilers to Proto transforms. Support for old Proto compilers is dropped.
Boost.Xpressive 從 Proto 編譯器修改為 Proto 變換。不再支持舊的 Proto 編譯器。

April 4, 2007

Preliminary submission of Proto to Boost.
給 Boost 的 Proto 初步提交物。

December 11, 2006

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上。

November 1, 2006

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。相關信息在 此處

October 28, 2006

Proto is reborn, this time with a uniform expression types that are POD. Announcement is here.
Proto 重生,這次將表達式類型為統一為POD。相關聲明在 此處

April 20, 2005

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 是否可以用類型為 AB 的參數來調用:

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.


PrevUpHomeNext