玩模板元編程走火入魔是一種怎樣的體驗?


其實寫起來和普通程序差不多啦~


用來玩俄羅斯方塊.

簡單介紹下, 每編譯一次遊戲就會進行一步.

操作是在編譯選項 -D 後面填相應的命令, 比如移動方塊, 旋轉, 下降到底等.

詳情參見GitHub - mattbierner/Super-Template-Tetris: Tetris as a C++ Template Metaprogram


體驗就是——被黑出翔了

我寫了一個庫叫template.scala,把C++的模板元編程搬到Scala上了。

微博上的人是這樣黑我的(https://twitter.com/yogthos/status/839180878760771584):


C++標準委員會也玩模板走火入魔,但是他們覺得這個門檻實在是太高了,應該讓寫編譯器的人去解決,讓全世界人民都可以低門檻玩轉模板元編程,於是就有了帶SFINAE和constexpr的函數。

於是現在就有了兩種C++編譯器

1、沒實現SFINAE+constexpr函數的編譯器

2、號稱實現了但是沒弄對的編譯器

(逃


有的程序,運行不運行已經無所謂了。

--------

update

見過最喪心病狂的應該是模版parser,完全跑在編譯時的那種,可以實現類型安全的printf。

至於編譯時如何處理字元串,可以看這個:https://github.com/hczhcz/reflectionpp/blob/master/reflection%2B%2B/static_str.hpp

順便,這個項目里另一個走火入魔的東西是,實現了一個編譯時mutable的變數。


-&> 輸出素數表

-&> 編譯期生成素數表

-&> 編譯錯誤信息生成素數表

#include &

template &
struct Check;

template &
struct Check&
{
enum { value = Check&::value };
};

template &
struct Check&
{
enum { value = false };
};

template &
struct Check&
{
enum { value = true };
};

template&
//struct Prime
//{
// enum { is_prime = Check&::value };
//}
;

template&
struct Prime&
{
enum { is_prime = false };
};

template &
struct PrintPrime
{
enum
{ count = PrintPrime&::count +
(Prime&::value&>::is_prime ? 1 : 0)
};
PrintPrime()
{
PrintPrime&();
if (Prime&::value&>::is_prime)
{
printf(" No.%d : %d
", count, i);
}
}
};

template &<&>
struct PrintPrime&<1&>
{
enum { count = 0 };
PrintPrime() {}
};

int main()
{
PrintPrime&<0x1f3&>(); // 499
}

在vs下,大概會得到這樣的信息:

…………………省略一堆………………
1&>main.cpp(41): error C2027: 使用了未定義類型「Prime&<397,true&>」
1&> main.cpp(41): note: 參見「Prime&<397,true&>」的聲明
1&>main.cpp(41): error C2027: 使用了未定義類型「Prime&<401,true&>」
1&> main.cpp(41): note: 參見「Prime&<401,true&>」的聲明
1&>main.cpp(41): error C2027: 使用了未定義類型「Prime&<409,true&>」
1&> main.cpp(41): note: 參見「Prime&<409,true&>」的聲明
1&>main.cpp(41): error C2027: 使用了未定義類型「Prime&<419,true&>」
1&> main.cpp(41): note: 參見「Prime&<419,true&>」的聲明
1&>main.cpp(41): error C2027: 使用了未定義類型「Prime&<421,true&>」
1&> main.cpp(41): note: 參見「Prime&<421,true&>」的聲明
1&>main.cpp(41): error C2027: 使用了未定義類型「Prime&<431,true&>」
1&> main.cpp(41): note: 參見「Prime&<431,true&>」的聲明
1&>main.cpp(41): error C2027: 使用了未定義類型「Prime&<433,true&>」
1&> main.cpp(41): note: 參見「Prime&<433,true&>」的聲明
1&>main.cpp(41): error C2027: 使用了未定義類型「Prime&<439,true&>」
1&> main.cpp(41): note: 參見「Prime&<439,true&>」的聲明
1&>main.cpp(41): error C2027: 使用了未定義類型「Prime&<443,true&>」
1&> main.cpp(41): note: 參見「Prime&<443,true&>」的聲明
1&>main.cpp(41): error C2027: 使用了未定義類型「Prime&<449,true&>」
1&> main.cpp(41): note: 參見「Prime&<449,true&>」的聲明
1&>main.cpp(41): error C2027: 使用了未定義類型「Prime&<457,true&>」
1&> main.cpp(41): note: 參見「Prime&<457,true&>」的聲明
1&>main.cpp(41): error C2027: 使用了未定義類型「Prime&<461,true&>」
1&> main.cpp(41): note: 參見「Prime&<461,true&>」的聲明
1&>main.cpp(41): error C2027: 使用了未定義類型「Prime&<463,true&>」
1&> main.cpp(41): note: 參見「Prime&<463,true&>」的聲明
1&>main.cpp(41): error C2027: 使用了未定義類型「Prime&<467,true&>」
1&> main.cpp(41): note: 參見「Prime&<467,true&>」的聲明
1&>main.cpp(41): error C2027: 使用了未定義類型「Prime&<479,true&>」
1&> main.cpp(41): note: 參見「Prime&<479,true&>」的聲明
1&>main.cpp(41): error C2027: 使用了未定義類型「Prime&<487,true&>」
1&> main.cpp(41): note: 參見「Prime&<487,true&>」的聲明
1&>main.cpp(41): error C2027: 使用了未定義類型「Prime&<491,true&>」
1&> main.cpp(41): note: 參見「Prime&<491,true&>」的聲明
1&>main.cpp(41): error C2027: 使用了未定義類型「Prime&<499,true&>」
1&> main.cpp(41): note: 參見「Prime&<499,true&>」的聲明

源於 Erwin Unruh 大神的 Primzahlen Original

——————————

通過我不斷改那個數字日編譯器,我發現vs2015最多(默認)只能被日到499,500他就受不了了= = ┑( ̄Д  ̄)┍

另,把注釋去掉就能運行了


題目說的是元編程,但好多回答的是泛型編程,既然如此,那我也強大下吧。

1:accumulation-dev/src/rpc at master · IronsDu/accumulation-dev · GitHub

rpc庫,註冊服務,無需處理反序列化和序列化(也就是無需手動組包和解析參數)(參數類型支持int,map,string,vector,tuple,protobuf,當然也就包括它們之間的任意組合)(目前底層用的序列化是RapidJson或MsgPack)

(其中很多地方,我個人覺得是我個人的模板技術的上限了)(斯認為模板不是去記住書上的規則,其實重要記性好,很容易記住。而是要知道怎麼/何時/合適的利用模板去解決問題,要充分發揮想像力~~這點就很難了)

伺服器:

static int add(int a, int b)
{
//CenterServerRPCMgr::getRpcFromer()為調用者會話對象
return a + b;
}

static void addNoneRet(int a, int b, dodo::RpcRequestInfo reqInfo)
{
// 添加dodo::RpcRequestInfo reqInfo形參(不影響調用者調用)
// 這裡本身不返回數據(函數返回類型為void),但RPC本身是具有返回值語義的
// 適用於需要調用其他非同步操作之後(通過reqInfo)才能返回數據給調用者的情況
// 譬如:
/*
auto caller = CenterServerRPCMgr::getRpcFromer();
redis-&>get("k", [caller, reqInfo](const std::string value){
caller-&>reply(reqInfo, value);
});
*/

/*
//客戶端:
centerServerConnectionRpc-&>call("test", 1, 2, [](const std::string value){
});
*/
}

void initCenterServerExt()
{
CenterServerRPCMgr::def("test", [](int a, int b){
return a + b;
});

CenterServerRPCMgr::def("testNoneRet", [](int a, int b){
});

CenterServerRPCMgr::def("add", add);

CenterServerRPCMgr::def("addNoneRet", addNoneRet);

客戶端:

gLogicCenterServerClient-&>call("add", 1, 2);
gLogicCenterServerClient-&>call("add", 1, 2, [](int result) {
}); // 最後一個參數可以是function類型,作為非同步回調(處理伺服器返回值),且咱不會把它作為參數發送給伺服器。

2:以前大學看的cdsn上作者叫 pandaxcl 寫的&<自動化C++程序設計&>,這就真是傳統意義上的元編程了,可惜現在blog無法訪問~~(貌似違規了


想把boost庫全部重新寫一遍.....

我已經重寫了boost的一部分了......

GitHub - yufengzjj/ATMPL: Another Template Meta Programming Library


模板元編程其實就是寫lisp嘛。。。感受就是調試超級痛苦(就算用的是clang)

GitHub - Cheukyin/TemplatedPL: A Simple Functional Programming Language Interpreter Based On C++ Template

這是我之前用模板元編程寫過的一個解釋器,可以定義高階函數,支持閉包和continuation,

在編譯期對表達式做CPS變換,所有結果都由編譯器推導出來

可正因為所有都是編譯器推導,根本就無法單步調試,

像下面這種超級複雜的模板嵌套,一旦寫錯一點,編譯器就會拋出幾十噸的error:

另外,模板根本就沒有什麼循環條件之類的常規結構,所有都得依靠遞歸和偏特化解決,

簡簡單單的數據結構都得寫得超複雜,還一堆尖括弧,一點都不直觀,隔個一兩月就忘了。。。

像下面一堆東西只是為了定義鏈表:

下面是為了定義變數綁定的continuation,反正現在我已經看不懂了。。。。。。


新建.cpp,條件反射template&<而不再是int main


想要編譯期 debugger


默默翻出多年前的代碼, 現在已經不敢讀了...

最大的體驗是微軟家的編譯器好脆弱...

編譯代碼一處錯誤報上上百行是幸運的情況, 畢竟是有的看...

Fatal Error C1001 次之, 好歹能試出來哪裡出的問題, 然後再連猜帶蒙的繞過這個bug...

最坑的是直接崩潰... 一按F7, 幾秒鐘之後VS彈個框告訴你編譯器掛掉了...

PROMISE_TEMPLATE_LIST
template &
auto PROMISE::then(OnFulfilled on_fulfilled, OnRejected on_rejected,
OnProgress on_progress, Attachments... attachments)
-&> std::enable_if_t&< _::ContinuationTypeTrait&::is_valid_continuation,
Category&::ResultType&>&> {
using namespace _;
using ResultType = typename ContinuationTypeTrait&< T, OnFulfilled, OnRejected, OnProgress, Attachments...&>::ResultType;
using ResultPromise = Category&;

auto default_on_fulfilled = [](ObjectifyVoid& v) {
return returnMaybeVoid(std::move(v));
};
using OnFulfilledType = decltype(forwardMaybeNull&(
std::forward&(on_fulfilled), std::move(default_on_fulfilled)));

auto default_on_rejected = [](std::exception_ptr e) {
std::rethrow_exception(e);
return returnMaybeVoid(
std::move(*reinterpret_cast&*&>(nullptr)));
};
using OnRejectedType = decltype(forwardMaybeNull&(
std::forward&(on_rejected), std::move(default_on_rejected)));

auto default_on_progress = [](ObjectifyVoid&) {};
using OnProgressType = decltype(forwardMaybeNull&(
std::forward&(on_progress), std::move(default_on_progress)));

auto old_node = node_.get();
auto new_node = ContinuationTypeTrait&::
NodeMaker&::make(
old_node-&>getEventLoop(), std::move(node_),
std::forward_as_tuple(std::forward&(attachments)...));
auto new_node_ptr = new_node.get();

old_node-&>setOnReadyCallback(partiallyApply(
[new_node_ptr](OnFulfilledType on_fulfilled, OnRejectedType on_rejected,
NodeType* node) {
auto result = node-&>getResult();
if (result.isException())
transformAndFulfill(new_node_ptr, on_rejected, result.getException());
else
transformAndFulfill(new_node_ptr, on_fulfilled, result.getValue());
},
forwardMaybeNull&(std::forward&(on_fulfilled),
std::move(default_on_fulfilled)),
forwardMaybeNull&(std::forward&(on_rejected),
std::move(default_on_rejected))));

old_node-&>setOnProgressCallback(
partiallyApply([new_node_ptr](OnProgressType on_progress, NodeType* node,
ObjectifyVoid& value) {
try {
callContinuation(on_progress, std::move(value),
new_node_ptr-&>getAttachment&<0&>());
}
catch (...) {
new_node_ptr-&>setException(std::current_exception());
}
},
forwardMaybeNull&(
std::forward&(on_progress),
std::move(default_on_progress))));

return ResultPromise{
std::unique_ptr&{std::move(new_node)}};
}

PROMISE_TEMPLATE_LIST
template &
auto PROMISE::then(OnFulfilled on_fulfilled)
-&> std::enable_if_t&< _::ContinuationTypeTrait&::is_valid_continuation,
Category&::ResultType&>&> {
return then(std::forward&(on_fulfilled), default_on_rejected_t{},
default_on_progress_t{});
}

PROMISE_TEMPLATE_LIST
template &
auto PROMISE::then(OnFulfilled on_fulfilled, OnRejected on_rejected)
-&> std::enable_if_t&< _::ContinuationTypeTrait&::is_valid_continuation,
Category&::ResultType&>&> {
return then(std::forward&(on_fulfilled),
std::forward&(on_rejected), default_on_progress_t{});
}


好多年沒寫template,不過還依稀記得當年元編程那本神書《Modern C++ Design》不得不在前言里附一個編譯器的連接,因為只有這個實驗室的前沿編譯器才能編譯書里的代碼,雖然這些例子都符合C++標準。

據說現在情況也沒有特別好轉。。


我對C++模板真的挺恐懼的,只知道最簡單的用在STL裡面的模板。不知道這段代碼算不算模板元編程:

出自G家程序員之手,聽說這個庫在G家程序員手裡應該是人手一份,願意是根據某個函數的參數類型自動生成一個回調函數,用於註冊事件的回調函數之用。

老實說,這樣的C++代碼,國內C++程序員能hold住的真不多,即使能hold住,gcc對模板的編譯出錯提示也很不友好,單查問題就能把你看哭了。

就我一個更崇尚C的程序員來看,還不如使用這樣的虛基類,基類中定義消息的類型和虛處理函數,收到消息時根據消息類型來查找註冊的回調函數呢:

class Message {
int type_;
virtual int Handle() = 0;

Message(int type) : type_(type) {

}
virtual ~Message();
}


噗,我來說一個Meta-Meta-Programming,用Racket寫marco來生成C++的Template。

http://matt.might.net/papers/ballantyne2014metameta.pdf


玩過一段時間,作為編程可玩性來說,不錯。

但是作為生產工具來說,性價比太低。


玩的時候寫的代碼現在已經不想改了.最大的感受就是編譯器和智能感知真脆弱.調試真痛苦


說實話,玩了幾年之後感覺太傷腦,智力遊戲罷了,能不用就不用了。


定義類時,敲的第一個字母是 t


模板元就是,想實現A語法,但是模板沒有直接A的東西,然後輾轉構造BCDEFG類型,然後一個套一個終於實現了A語法,然後感覺運行起來快了那麼一丟丟,好滿足呀。


推薦閱讀:

如何用c++ template 解決這個需求?

TAG:C | 元編程 | D編程語言 | 模板C |