Smiley face

我的征尘是星辰大海。。。

The dirt and dust from my pilgrimage forms oceans of stars...

-------当记忆的篇章变得零碎,当追忆的图片变得模糊,我们只能求助于数字存储的永恒的回忆

作者:黄教授

二〇二二


一月一日 等待变化等待机会

  1. 这个是我经常查阅c++语法的网站,可是我总也记不住就置顶在这里吧。
  2. 之前使用操作符优先级的确解决了几个shift的冲突,我的确看到了在relational-expression vs equality-expression之间的冲突得到了解决。这个当然是最最基本的,我只是实证了这样子的冲突解决方式的必要性。据说从语法上c++/GCC都不做优先级的标明,但是这个应该是隐含在代码的逻辑里的吧?
  3. 这里的merge理论还是很深奥的,我一时还是似懂非懂。我实际制作这个范例,但是还是不明所以然。只是认识到一点就是使用%dprec的确解决了冲突,但是它和%merge是什么关系?
    %dprec
    Bison declaration to assign a precedence to a rule that is used at parse time to resolve reduce/reduce conflicts.
    %merge
    Bison declaration to assign a merging function to a rule. If there is a reduce/reduce conflict with a rule having the same merging function, the function is applied to the two semantic values to get a single result.
    首先,就是我昨天试图给terminal和nonterminal之间添加优先级的尝试是非常幼稚天真的,这个给了你在rule排列优先级的选项。虽然不是nonterminal之间但是这个似乎更加的细分而具体。其次,我们现在讨论的是reduce/reduce而牵扯到操作符优先级的是shift/reduce,这个是两个完全不同的范畴。最后,这个merge函数是为了什么而存在的呢?如果parser不去解决sematic的设定问题你还要parser做什么?所以,很明显的我们的最终目的是解决semantic的问题,也许通过不同路径到达一个相同的结果不一定给予了最终的那个identical的状态赋予相同的semantic的动作,那么bison这个时候给我们机会让我们再次审视取舍。我想这个就是我的理解。怎么做实验来验证呢?bison对于这些高深的部分惜墨如金讲的不是很具体,或许我的领悟力不够看不大明白吧?
  4. 我再读一遍似乎明白一点点,就是如果你确定两个rule之间的优先级取舍,那么你可以使用%dprec来表明你的决定。然而如果你并不是那么确定的情况下,你不妨把这个决策推迟在一个类似回调函数的merge函数里,当然我觉得两者共同的前提是两条路径都是合法的而且殊途同归到达了一个完全相同的状态,这个时候就是我常常幻想要解决的问题,从本质上看我们走了两条路,是否真的有歧义呢?也许并不是语法的问题?因为我们得到了相同的结果,我觉得这个才是最重要的因为我们只关心如何设定semantic的值,那么在我们从两条不同的路径得到的结果也许有不同,那么使用一个merge函数也许能够帮助我们做矫正。所以,总的来说GLR已经解决了问题,但是它不是很确定这个结果是否是用户想要的所以才有这个merge的动作,我在yyresolveValue这个函数里看到大概的逻辑,就是调用yypreference来比较两者的所谓的semantic_option,如果用户没有做任何的设定就代表有冲突报告冲突,如果用户有针对两者设定了相同的merge函数则调用用户的merge函数,否则就按照用户设定的优先级来选择其所连带的semantic值。我的理解就是要针对两条路径得到相同的结果的路径上的semantic做合并吧?
  5. 之前我认为.y文件产生的头文件就是给第三方使用的完全没有必要给自己产生的parser代码使用,现在看来是不对的,比如YYSTYPE这个宏在头文件里定义了,如果你要使用它,那么你只能在你的parser里再次引用自己的头文件才行。这个是我以前所不理解的。
  6. 脑子是混乱的,因为我折腾了好久才意识到可能的问题,就是说value_type被定义为non-copyable,直接把操作符=delete了,结果导致我定义的merge函数返回value_type成为不可能,而且根本看不出有什么办法解决这个问题。我迷惑了好久才再次看官方的样板意识到要使用自己定义的类型才行,这个不失为一个解决方案,但是要看懂这个例子我又糊涂了。
  7. 大师在代码里有一个链接要隐藏类型,我还没有时间看细节。估计是接口实现分离之类的吧?

一月二日 等待变化等待机会

  1. 一个早上都被一个遍历链接的问题折磨。应该是很简单的,可是我始终搞不明白,这里的lexer需要用到parser的头文件,而parser的头文件里定义了一些模板类就是大师的那些Node/Nterm那么我就又需要在头文件里引用这个额外的头文件,结果在链接时候lever.o和parser.o就把这些模板类都定义了一遍。结果链接就有问题,可是我要怎么安排呢?我怀疑也许还有别的问题也许链接仅仅是小问题。可是。。。
  2. 折腾了很久我最后终于采取一个断然措施:我决定把两个代码文件合并成一个来编译!因为模板无法分离声明和实现,难道这个是唯一的做法吗?或许我有什么地方理解错了?先保存这个实验,我觉得相当的重要!

一月三日 等待变化等待机会

  1. 也许直到现在才第一次接触到深水区,因为我期待能够在所有的规则都使用一个无害的%merge然而官方例子似乎只能允许一个%empty空规则,coredump是一件好事因为它暴露了问题,也许是逻辑的问题,也许是那个简单的Node实现的问题,这些都是好事,最糟糕的是GLR的这种分进合击的算法的问题,这个需要艰苦的debug,在这之前使用%no-lines帮助正常的代码定位是必要的。还是先保存一个版本吧?
  2. 其实这个是很简单的一个技巧,注释说的一清二楚,可是我就是看不懂,看来我对于SFINAE还是不够敏感。 我非要把这个type做了如同2+2=4才能理解!也就是说瑞与任何的类型T如果不是Node你可以得到一个无用的默认参数int*ptr=nullptr 可是如果TNode这个是不成立的代码!而我一开始没看清楚还以为是Node*实际上enable_if_t在条件不成立的时候返回的不是void而是不成立的代码ill-formed,因为根本没有定义type! 这里的模板的general形式没有定义type,只有在partial specialization的时候才有定义, 这个是每一个c++程序员应该熟练掌握的基本问题,我却总是忘记,因为我脑海里始终被那个typename _Tp = void困扰,每次总是认为在条件不成立的时候返回的type是void,这个也许是以前看视频里看到大师介绍void_t的时候留下的阴影吧?这个真的是无厘头,我不知道为什么这两个会被联想到一起?实际上这个暴露了我对于void_t始终没有理解!
  3. 我几乎是每一次看SFINAE代码就有惊为天人的感觉,我自认为几乎一辈子都无法真正写一个我自己的SFINAE的代码。
  4. 这个宏是我不知道的:

    A macro can be declared to accept a variable number of arguments much as a function can. The syntax for defining the macro is similar to that of a function. Here is an example:

    #define eprintf(...) fprintf (stderr, __VA_ARGS__)
    
    这个例子更加的有用:

一月四日 等待变化等待机会

  1. 我使用我自己定义的Node依然不行,决定向大师报告看看有没有希望。保存并准备看看glr1.cc是否有希望。

一月五日 等待变化等待机会

  1. 欧染风寒。我尝试了一下lalr1.cc这个才让我真实的明白了一个简单的道理:它就是LALR(1)的算法也就是说它认为语法没有冲突它只会选择每次的第一个选项直到出错。而GLR的高明之处是每次在冲突的时候分支看看结果如何。不过问题是似乎GLR的算法还在完善之中,或者说最起码它的c++版本还在开发中因为glr2.cc的crash我现在认为是因为variant的实现的问题。但是当我退回glr1.cc的时候不支持variant意味着我也不能使用constructor结果我的代码又要进行大的改动。我想先保存一个版本再说。

一月六日 等待变化等待机会

  1. 总算完成了glr的实验,说穿了glr.cc实际上和glr.c应该区别不大,也就是说依旧使用union的做法,导致你要很多地方使用纯粹c语言的做法,指针来指针去。而一个大的进步是我观察到了导致crash的现象似乎是glr算法的分离stack有问题,导致死循环。在进一步挖掘之前先保存一下吧。稍微修改了一下看到在没有merge的情况下也会crash,这个发现很有意义,也许可以debug一下?
  2. 我找到一个陈年的帖子,也许有参考意义,就是说语法里并不是随便添加一个empty就会产生歧义。
  3. 看到一个貌似有趣的问题,吃完饭再看看。
  4. 大师的讲义这是在c++之父在他创建的语言的40岁生日的演讲,是有重大意义的。

一月七日 等待变化等待机会

  1. gdb下几乎没有解不开的谜团,因为确定无疑的crash是最容易解决的问题之一,首先,它确定就不是随机错误。其次,它能crash说明是没有预计的意外。最后,十有八九它是debug的问题,因为它也许本身就是debug编译下的副产品。我昨天遇到的就是这个问题,这个是输出打印的问题,这个似乎是好消息因为它不是算法本身的问题,也未毕是variant的问题,因为它是显示输出的问题。所以,我把我的定义改了一下: %printer { if (yyvaluep) yyo << $$; } <node>这里使用yyvaluep因为它就是bison产生的代码函数的参数名字而不是一个全局变量之类的,只能说这个就是目前bison编程的范式,说到底它类似于宏的各种替换,但是这个实在是没有办法的办法。因为在我的union的定义下$$实际上等价于(*yyvaluep).node因为我的union是%union {Node* node;};,所以,这个问题还是容易解决的。我决定再看看之前的variant模式下是否可以解决。看来没有那么容易,这个variant的机制远比我想像的复杂因为有好几个因素,一个是内存释放的机制一个是我自己使用shared_ptr的问题,而且我对于std::move的使用还是属于一知半解,大师在这方面的代码有些玄奥,我尝试自己做了一个clone来处理每一个shared_ptr的复制并且重载了const reference和rvalue-reference两种情况分别使用reset和swap,但是我觉得我的理解也许还是不对,照理说我如果传递参数类型做到perfect forwarding我是不应该担心这个clone问题的,除非bison自己的机制。。。我觉得我还是使用c语言接口或者是那个简单的c++包装的glr.cc吧?毕竟算法比使用复杂的c++variant根本不是直接的variant而是bison自己实现的variant,这个风险不值得。我还是先保存一个版本吧。
  2. 至少现在编译没有问题了,中间遇到一个在lexer里报怨YY_NULL转换的错误,这个是我以前没有遇到的,后来才发现是所谓的yyterminate没有的问题,但是解决的方法竟然是我要在我的lexer里面定义<<EOF>>的规则来返回yy::parser::token::TOK_YYEOF;,这其中的逻辑我还一时看不太懂。总之先对付过去再说。能不能说我现在初步完成了一个似乎能够工作的版本呢?我使用了一个特殊的起始的nonterminal来作为打印输出,从结果可以大致看出parser经历的路径以及融合的过程吧?
  3. 今天总算完成了一些什么,可以好好的休息一下吧?这里保存一个版本吧。

一月八日 等待变化等待机会

  1. 现在听大师讲解最基本的lambda感觉没有什么困难了,说明我比以前有了很大的进步了。大师有一个小小的课后作业:创造一个在heap的lambda,因为通常的lambda都是在stack的。这个显然是很容易的: 唯一需要注意的是(*plam)(1,2)这个lambda的指针在作为lambda使用的时候最好先括号起来防止可能的歧义?这是因为dereference也就是*的优先级比函数调用的括号()来的低导致我们会看到编译器报怨plam(1,2)不能作为函数调用因为plam是一个指针而已。这位大师应该是来自于俄罗斯的大神吧?我记得我以前买过他的关于模板的书。
  2. 大师的演讲里另一个很有趣的地方是我之前不知道的东西,就是怎么定义一个stateful的lambda呢?这个帖子的方法虽然都可以但是大师的做法是正宗的。这个也是另一个回答的方法就是使用lambda自身的局部变量加上mutable,但是这个引发了一个副产品就是你的lambda现在不再是一个const类型了,捕捉的时候需要引用[&lam]而不能是拷贝[lam]。所以,我的做法是把这个mutable的lambda放在lambda内部

一月九日 等待变化等待机会

  1. 我打算现在回过头来再次研究一下怎么处理之前的头疼的问题,其中包含了纯虚函数声明里的那个=0要怎么处理,我头疼的厉害,然后就看大师的注解里第一条就提到说完全不需要在lexer里面专门为'0'这个integer-literal专门开后门,而是似乎要在后面的分析中解决它?
    The lexer need not treat '0' as distinct from IntegerLiteral in the hope that pure-specifier can be distinguished, It isn't. Semantic rescue from = constant-expression is necessary.
    这里实在是天机不可泄露因为大师的语法已经改的面目全非,我根本找不到虚函数声明的部分,我唯一得到的启示就是别管它了不能因为这么一条语法就破坏了规矩,还是把它简化为literal吧?或者Integer-literal/character-literal,然后我就发现之前我放过的user-defined-literal,这里的问题才多呢?而这个例子让我震惊了一下! 如果我们只是定义了这么三个函数的话
    
    long double operator "" _w(long double);
    std::string operator "" _w(const char16_t*, size_t);
    unsigned operator "" _w(const char*);
    
    那么 12_w;调用的是哪一个呢?居然是最后一个!直到我再次看那些精微奥妙的解释我才明白:对于long double来说是一个特例,其他都必须使用函数参数为const char*作为纯粹的string literal来传递参数。且慢!我真的看懂了吗?这里说的是模板函数啊!那么要怎么定义模板函数呢?我还真的被难住了。我必须要特殊化,那么原型是什么呢? 然后我要怎么特殊化呢? 注意这样子的话,和我们原来的例子的所谓的raw literal operator就冲突了,这个就是raw literal operator std::string operator "" _w(const char* ptr) 所以,模板和raw-literal-operator只能二选一!我看到这里简直头疼死了!这里不是简单的特例化的实现而是要实现整个模板啊!没有解决lexer的问题反而牵扯出这么多的user-defined-literal的问题!
  2. 重新修改了一下literal,其实就是返回更加详细的种类,这个对于我来说本来是无意义的因为我根本没有能力去做语义分析部分,所以,任何种类的literal对我来说没有区别,甚至于我把纯虚函数的=0都改成了=INTEGER_LITERAL这个是我第一次不得已修改放宽语法,因为在我看来这个问题很难解决,大师的意思我猜测就是依靠在后续的变量赋值或者初始化语句来分析排除。然后遇到一个让我抓狂的错误,后来才恍然大悟,这个是GNU的扩展啊!我压根儿没有定义__attribute__,而且即便是标准定义的这些属性也不是语法需要更新的因为它们本来就像是变量名一样的。
  3. 然后我又一次遇到令人头疼的header-name的问题,这个本质上就是module引起的,我至今也对于module的import是否是预处理还是压根就是要怎样不是很清楚,我以前的理解是它们先要转换为一个特殊的token然后传统的tokenizer才能处理,这个包含把import改为import-key之类的,但是我投机没有这么做,然后面对header-name就自然的和模板的参数符号<>产生冲突了,头疼啊。看来的确是预处理的工作。如果我理解不错的话就是module家族里pp-import是部分存在于传统的预处理范畴的,尽管它的实现机制也许相当的复杂需要延续到编译的后面环节,但最起码它是需要预处理方式来tokenize的。我也只能去改了这个没法办太复杂了因为我在tokenizer是返回一个个token而不是批量处理它们。头疼啊。。那么我把header-name返回到parser来处理呢?语法本来也是这么定义的,我是投机才把它放到了lexer里去做token。
    The sequences in both forms of header-names are mapped in an implementation-defined manner to headers or to external source file names as specified in [cpp.include].
    标准怎么说的?没有那么简单,这个是要直接把它处理成好像include一样成为文件的表达,这里我一直依赖于gcc预处理来处理include,那么也是没有能力处理这个import-header-name了。
  4. 我出去走了走就想明白了,这个世界如果你解决不了的问题就把它搁置起来,比如这个header-name我无法对付就把它在lexer里除名好了,暂时不需要更动语法部分。而对于GNU对于c/c++的扩展需不需要支持倒是一个现实的问题。
  5. 这个问题我总是忘记!
    Token types are in libcpp/include/cpplib.h (TTYPE_TABLE), keywords in gcc/c-family/c-common.h (enum rid).
    gcc/c-family/c-common.c里可以看到这样一个结构c_common_resword c_common_reswords而且__attribute__attribute__是等价的! 这个真的是令人尴尬的因为我花了好长时间反复搜索代码也找不到这个答案,因为我并不知道要怎么才能找到它们,大概是enum之后大小写不同了吧?
  6. 关于语法这么一行字怎么说明呢?
    This __attribute__ keyword is followed by an attribute specification enclosed in double parentheses.
    这个太头疼了。。。clang几乎是克隆GCC的但是MSVC++显然不是的,它用的是__declspec

    __attribute__ is not a macro, it is a GCC specific extension that needs to be replaced with whatever equivalent Visual C++ has. That equivalent is usually __declspec:

    http://msdn.microsoft.com/en-US/library/dabb5z75(v=vs.110).aspx

    AFAIR GCC's __attribute__ has different placement than Visual C++'s __declspec so I'm not sure if you can simply use a macro to "convert" __attribute__ to __declspec.

  7. 头疼啊,头疼!大部分都是预处理相关的问题,其实前端真的很麻烦的。当然后端更加的复杂因为你根本就不懂。看得懂的困难叫麻烦,看不懂的甚至看不到的困难叫做一个闷棍,没遇到根本不知道它的存在,遇到了就当场昏死过去了。

一月十日 等待变化等待机会

  1. 似乎世界上就不存在纯粹ISO语法的c++,也许古代还有,因为我尝试boost也看到很多的__attribute__很可能需要用到stl,而这个显然是本地化了。这个语法先记录下来:

    An attribute specifier is of the form __attribute__ ((attribute-list)). An attribute list is a possibly empty comma-separated sequence of attributes, where each attribute is one of the following:

    • Empty. Empty attributes are ignored.
    • An attribute name (which may be an identifier such as unused, or a reserved word such as const).
    • An attribute name followed by a parenthesized list of parameters for the attribute. These parameters take one of the following forms:
      • An identifier. For example, mode attributes use this form.
      • An identifier followed by a comma and a non-empty comma-separated list of expressions. For example, format attributes use this form.
      • A possibly empty comma-separated list of expressions. For example, format_arg attributes use this form with the list being a single integer constant expression, and alias attributes use this form with the list being a single string constant.

    An attribute specifier list is a sequence of one or more attribute specifiers, not separated by any other tokens.

    You may optionally specify attribute names with ‘__’ preceding and following the name. This allows you to use them in header files without being concerned about a possible macro of the same name. For example, you may use the attribute name __noreturn__ instead of noreturn.

    
    gnu-attribute-specifier:
    	__attribute__ ((gnu-attribute-list))
    gnu-attribute-list:
    	%empty
    	|	IDENTIFIER
    	|	IDENTIFIER (gnu-attribute-parameters)
    gnu-attribute-parameters:
    	IDENTIFIER
    	|	gnu-attribute-parameters , IDENTIFIER	
    
    语法里提到的all other attributes让我看的头昏眼花,太复杂了。都已经这么多年了难道GCC都不能说服标准委员会接受这个扩展吗?我知道这个肯定是不行的,因为换作是我也会坚决反对的,这个实在是太混乱了。

一月十一日 等待变化等待机会

  1. 世界上从来只有建设最难,破坏其实很容易。所以,对于无法处理的问题拆除是最简单的。于是我决定放弃增添GNU的扩展语法,因为太复杂了,需要添加在非常多的地方,所以,我花了一点时间在lexer里把它过滤掉了,这个就容易多了。然后我就遇到了这个语法错误,我说实在的这个真的把我难倒了我从来没有认真用过这种函数声明里的throw语法,之前工作里的代码我也是很反对,感觉没有什么用途。 void foo() throw ();这个为什么是非法的呢?因为我看我的语法反复好几次都找不出这个形式直到这里才恍然大悟:这个是字从c++11就宣布deprecated,到了c++17就被删除了!那怎么办?对于GNU的__attribute__扩展我可以在lexer里过滤掉,但是对于兼容的语法我要怎么办?
  2. 我反复看到这个DWS的自我炫耀的帖子了,不过人家是有资格炫耀的。这篇帖子《Life After Parsing™: Got My Grammar... uh, now what?》绝对的干货,只有亲自尝试过的人才能体会他的金玉良言!我很可能已经读过这篇博文好几次了吧?

    DMS uses a GLR parser, which means it handles arbitrary lookahead and ambiguous grammars with aplomb. Arbitrary semantic reduction conditions allow DMS parsers to eliminate many ambiguous choices while parsing, or they can be retained an[d] eliminated later using semantic information.

    DMS parsers can be configured to collect preprocessor definitions and conditionals without expanding them. This makes it possible to reason about the code as seen by the programmer, with all the preprocessor directives in place. No other tool known to us can do this.

    难道DMS也写了自己的preprocessor吗?我想应该是必然的,没有道理不去,如果连预处理这么简单的复杂工作都做不了还谈什么后续的工作?
    Representation Capture:
    Real tools require more than just "parsing"; the structure of the program must be captured to enable further processing. Usually an Abstract Syntax Tree (AST) is needed. Typical parser generators force the grammar engineer to specify not only the grammar, but also to explicitly specify how to procedurally build the AST as well. This essentially (more than) doubles the work, and it makes maintaining the grammar hard because any grammar changes require the AST building code as well. In contrast, DMS automatically builds a syntax tree, either a concrete tree ("CST", mainly for debugging, containing all the language tokens), or what amounts to an AST (for production, in which non-value carrying terminals are eliminated, useless unary productions are removed, and lists are formed). Changes to the grammar automatically cause the C/AST to change correspondingly. And the tree is always correct with respect to the grammar. It includes precise source line information (file/line/column) as well as comments passed to it by the lexer, attached to appropriate tree nodes, and it can include preprocessor directives.
  3. 这个公司提供了个很变态的例子来说明他们产品的强悍 也就是说这里的typedef的类型是一个条件变数,当条件为true的时候a::b实际上是一个模板类,它需要一个整数模板无类型参数所以,这里的c必须是有值的整数,通常使用enum的好处是它第一个元素初始化为0了。但是当条件为false的时候a::b指的是一个无名的enum,那么这个时候使用比较符号比较无名的enum和无名enum之间还是会有编译器的警告的,我觉得这个纯粹是GCC实现的特异性,因为这里说了:
    enumerator-list
    comma-separated list of enumerator definitions, each of which is either simply an identifier, which becomes the name of the enumerator, or an identifier with an initializer: identifier = constexpr. In either case, the identifier can be directly followed by an optional attribute specifier sequence. (since C++17)
    There are two distinct kinds of enumerations: unscoped enumeration (declared with the enum-key enum) and scoped enumeration (declared with the enum-key enum class or enum struct).
    1. Declares an unscoped enumeration type whose underlying type is not fixed (in this case, the underlying type is an implementation-defined integral type that can represent all enumerator values; this type is not larger than int unless the value of an enumerator cannot fit in an int or unsigned int. If the enumerator-list is empty, the underlying type is as if the enumeration had a single enumerator with value 0).
    2. Declares an unscoped enumeration type whose underlying type is fixed.
    3. Opaque enum declaration for an unscoped enumeration must specify the name and the underlying type.
    总之,我压根无法获得那个无名的enum的成员的类型。这个问题问到点子上了,因为我从来就感觉不到使用无名的enum的作用,仅仅为了隐藏名字吗?这里的大侠们一致说是为了声明常量而用的。 anonymous enums are usually used when the only purpose of the enum is to generate constants. 这个值得学习,因为使用const还是使用无名enum呢?
  4. 至此可以告一个段落吧?我验证了一个基本上无用的东西,使用GLR可以原则上对于正确代码做出基本的肯定,或者说有可能达到最好的结果就是positive-positive=>100%这个等价于positive-negative=>0%,但是对于明显不符合语法的错误代码也许也能够做到negative-negative=>100%,这个就是这个思路的最理想的结果,至于说negative-positive会多少我不可能知道。那么这个东西有任何实际价值吗?
  5. 大侠的博文让人心虚甚至是颤抖:But success in parsing and tree building is such a small part of the problem that a victory really means very little. 但是单单这个parsing就是如此的困难,还要说什么呢?我实际上几乎什么也没有做因为单单准备工作就让人晕头转向。
  6. 我一直对于location感到混乱,实际上是很简单的一件事情,当然头脑要清楚两个不同的行,我目前是对于输入文件的行也就是flex的行,我想在bison错误的输出是打印:
    1. 首先要在flex里激活这个机制:%option yylineno这样子你不必在lexer语法里自己实现因为flex会帮你实现,而你可以直接使用这个当前的行,如果你知道它的变量名的话:yylineno
    2. 所以,第二步就是让bison来使用这个变量,你可以声明它在你的bison的产生的编译器的代码里:extern int yylineno;
    3. 怎么让bison的输出错误打印这个行号呢?其实非常简单,因为你必须为bison定义一个error函数,所以:
    4. 最后需要提醒的是bison的选项%locations是一个完全不同的范畴,它是关于语法文件的部分的行号吧?总之,这个是我目前还不需要的一个步骤。
  7. 在我的测试里总是遇到内存消耗光的错误,于是我在我的Makefile里编译的时候加上了这个宏的定义-DYYMAXDEPTH=80000 此外bison手册里说要用left-recursion而不要用right-recursion
    Any kind of sequence can be defined using either left recursion or right recursion, but you should always use left recursion, because it can parse a sequence of any number of elements with bounded stack space. Right recursion uses up space on the Bison stack in proportion to the number of elements in the sequence, because all the elements must be shifted onto the stack before the rule can be applied even once.
  8. 我对于我的程序不是很有信心结果遇到了这个问题,我曾经反复看到这些所谓的keyword的问题。
    The keywords asm, typeof and inline are not available in programs compiled with -ansi or -std (although inline can be used in a program compiled with -std=c99 or a later standard). The ISO C99 keyword restrict is only available when -std=gnu99 (which will eventually be the default) or -std=c99 (or the equivalent -std=iso9899:1999), or an option for a later standard version, is used.
    这是什么意思呢?我看不懂!
    我打算抄录这些keywords
    
    /* Reserved words.  The third field is a mask: keywords are disabled
       if they match the mask.
    
       Masks for languages:
       C --std=c89: D_C99 | D_CXXONLY | D_OBJC | D_CXX_OBJC
       C --std=c99: D_CXXONLY | D_OBJC
       ObjC is like C except that D_OBJC and D_CXX_OBJC are not set
       C++ --std=c++98: D_CONLY | D_CXX11 | D_CXX20 | D_OBJC
       C++ --std=c++11: D_CONLY | D_CXX20 | D_OBJC
       C++ --std=c++20: D_CONLY | D_OBJC
       ObjC++ is like C++ except that D_OBJC is not set
    
       If -fno-asm is used, D_ASM is added to the mask.  If
       -fno-gnu-keywords is used, D_EXT is added.  If -fno-asm and C in
       C89 mode, D_EXT89 is added for both -fno-asm and -fno-gnu-keywords.
       In C with -Wc++-compat, we warn if D_CXXWARN is set.
    
       Note the complication of the D_CXX_OBJC keywords.  These are
       reserved words such as 'class'.  In C++, 'class' is a reserved
       word.  In Objective-C++ it is too.  In Objective-C, it is a
       reserved word too, but only if it follows an '@' sign.
    */
    const struct c_common_resword c_common_reswords[] =
    {
      { "_Alignas",		RID_ALIGNAS,   D_CONLY },
      { "_Alignof",		RID_ALIGNOF,   D_CONLY },
      { "_Atomic",		RID_ATOMIC,    D_CONLY },
      { "_Bool",		RID_BOOL,      D_CONLY },
      { "_Complex",		RID_COMPLEX,	0 },
      { "_Imaginary",	RID_IMAGINARY, D_CONLY },
      { "_Float16",         RID_FLOAT16,   D_CONLY },
      { "_Float32",         RID_FLOAT32,   D_CONLY },
      { "_Float64",         RID_FLOAT64,   D_CONLY },
      { "_Float128",        RID_FLOAT128,  D_CONLY },
      { "_Float32x",        RID_FLOAT32X,  D_CONLY },
      { "_Float64x",        RID_FLOAT64X,  D_CONLY },
      { "_Float128x",       RID_FLOAT128X, D_CONLY },
      { "_Decimal32",       RID_DFLOAT32,  D_CONLY },
      { "_Decimal64",       RID_DFLOAT64,  D_CONLY },
      { "_Decimal128",      RID_DFLOAT128, D_CONLY },
      { "_Fract",           RID_FRACT,     D_CONLY | D_EXT },
      { "_Accum",           RID_ACCUM,     D_CONLY | D_EXT },
      { "_Sat",             RID_SAT,       D_CONLY | D_EXT },
      { "_Static_assert",   RID_STATIC_ASSERT, D_CONLY },
      { "_Noreturn",        RID_NORETURN,  D_CONLY },
      { "_Generic",         RID_GENERIC,   D_CONLY },
      { "_Thread_local",    RID_THREAD,    D_CONLY },
      { "__FUNCTION__",	RID_FUNCTION_NAME, 0 },
      { "__PRETTY_FUNCTION__", RID_PRETTY_FUNCTION_NAME, 0 },
      { "__alignof",	RID_ALIGNOF,	0 },
      { "__alignof__",	RID_ALIGNOF,	0 },
      { "__asm",		RID_ASM,	0 },
      { "__asm__",		RID_ASM,	0 },
      { "__attribute",	RID_ATTRIBUTE,	0 },
      { "__attribute__",	RID_ATTRIBUTE,	0 },
      { "__auto_type",	RID_AUTO_TYPE,	D_CONLY },
      { "__bases",          RID_BASES, D_CXXONLY },
      { "__builtin_addressof", RID_ADDRESSOF, D_CXXONLY },
      { "__builtin_bit_cast", RID_BUILTIN_BIT_CAST, D_CXXONLY },
      { "__builtin_call_with_static_chain",
        RID_BUILTIN_CALL_WITH_STATIC_CHAIN, D_CONLY },
      { "__builtin_choose_expr", RID_CHOOSE_EXPR, D_CONLY },
      { "__builtin_complex", RID_BUILTIN_COMPLEX, D_CONLY },
      { "__builtin_convertvector", RID_BUILTIN_CONVERTVECTOR, 0 },
      { "__builtin_has_attribute", RID_BUILTIN_HAS_ATTRIBUTE, 0 },
      { "__builtin_launder", RID_BUILTIN_LAUNDER, D_CXXONLY },
      { "__builtin_shuffle", RID_BUILTIN_SHUFFLE, 0 },
      { "__builtin_shufflevector", RID_BUILTIN_SHUFFLEVECTOR, 0 },
      { "__builtin_tgmath", RID_BUILTIN_TGMATH, D_CONLY },
      { "__builtin_offsetof", RID_OFFSETOF, 0 },
      { "__builtin_types_compatible_p", RID_TYPES_COMPATIBLE_P, D_CONLY },
      { "__builtin_va_arg",	RID_VA_ARG,	0 },
      { "__complex",	RID_COMPLEX,	0 },
      { "__complex__",	RID_COMPLEX,	0 },
      { "__const",		RID_CONST,	0 },
      { "__const__",	RID_CONST,	0 },
      { "__constinit",	RID_CONSTINIT,	D_CXXONLY },
      { "__decltype",       RID_DECLTYPE,   D_CXXONLY },
      { "__direct_bases",   RID_DIRECT_BASES, D_CXXONLY },
      { "__extension__",	RID_EXTENSION,	0 },
      { "__func__",		RID_C99_FUNCTION_NAME, 0 },
      { "__has_nothrow_assign", RID_HAS_NOTHROW_ASSIGN, D_CXXONLY },
      { "__has_nothrow_constructor", RID_HAS_NOTHROW_CONSTRUCTOR, D_CXXONLY },
      { "__has_nothrow_copy", RID_HAS_NOTHROW_COPY, D_CXXONLY },
      { "__has_trivial_assign", RID_HAS_TRIVIAL_ASSIGN, D_CXXONLY },
      { "__has_trivial_constructor", RID_HAS_TRIVIAL_CONSTRUCTOR, D_CXXONLY },
      { "__has_trivial_copy", RID_HAS_TRIVIAL_COPY, D_CXXONLY },
      { "__has_trivial_destructor", RID_HAS_TRIVIAL_DESTRUCTOR, D_CXXONLY },
      { "__has_unique_object_representations", RID_HAS_UNIQUE_OBJ_REPRESENTATIONS,
    					D_CXXONLY },
      { "__has_virtual_destructor", RID_HAS_VIRTUAL_DESTRUCTOR, D_CXXONLY },
      { "__imag",		RID_IMAGPART,	0 },
      { "__imag__",		RID_IMAGPART,	0 },
      { "__inline",		RID_INLINE,	0 },
      { "__inline__",	RID_INLINE,	0 },
      { "__is_abstract",	RID_IS_ABSTRACT, D_CXXONLY },
      { "__is_aggregate",	RID_IS_AGGREGATE, D_CXXONLY },
      { "__is_base_of",	RID_IS_BASE_OF, D_CXXONLY },
      { "__is_class",	RID_IS_CLASS,	D_CXXONLY },
      { "__is_empty",	RID_IS_EMPTY,	D_CXXONLY },
      { "__is_enum",	RID_IS_ENUM,	D_CXXONLY },
      { "__is_final",	RID_IS_FINAL,	D_CXXONLY },
      { "__is_layout_compatible", RID_IS_LAYOUT_COMPATIBLE, D_CXXONLY },
      { "__is_literal_type", RID_IS_LITERAL_TYPE, D_CXXONLY },
      { "__is_pointer_interconvertible_base_of",
    			RID_IS_POINTER_INTERCONVERTIBLE_BASE_OF, D_CXXONLY },
      { "__is_pod",		RID_IS_POD,	D_CXXONLY },
      { "__is_polymorphic",	RID_IS_POLYMORPHIC, D_CXXONLY },
      { "__is_same",     RID_IS_SAME_AS, D_CXXONLY },
      { "__is_same_as",     RID_IS_SAME_AS, D_CXXONLY },
      { "__is_standard_layout", RID_IS_STD_LAYOUT, D_CXXONLY },
      { "__is_trivial",     RID_IS_TRIVIAL, D_CXXONLY },
      { "__is_trivially_assignable", RID_IS_TRIVIALLY_ASSIGNABLE, D_CXXONLY },
      { "__is_trivially_constructible", RID_IS_TRIVIALLY_CONSTRUCTIBLE, D_CXXONLY },
      { "__is_trivially_copyable", RID_IS_TRIVIALLY_COPYABLE, D_CXXONLY },
      { "__is_union",	RID_IS_UNION,	D_CXXONLY },
      { "__label__",	RID_LABEL,	0 },
      { "__null",		RID_NULL,	0 },
      { "__real",		RID_REALPART,	0 },
      { "__real__",		RID_REALPART,	0 },
      { "__restrict",	RID_RESTRICT,	0 },
      { "__restrict__",	RID_RESTRICT,	0 },
      { "__signed",		RID_SIGNED,	0 },
      { "__signed__",	RID_SIGNED,	0 },
      { "__thread",		RID_THREAD,	0 },
      { "__transaction_atomic", RID_TRANSACTION_ATOMIC, 0 },
      { "__transaction_relaxed", RID_TRANSACTION_RELAXED, 0 },
      { "__transaction_cancel", RID_TRANSACTION_CANCEL, 0 },
      { "__typeof",		RID_TYPEOF,	0 },
      { "__typeof__",	RID_TYPEOF,	0 },
      { "__underlying_type", RID_UNDERLYING_TYPE, D_CXXONLY },
      { "__volatile",	RID_VOLATILE,	0 },
      { "__volatile__",	RID_VOLATILE,	0 },
      { "__GIMPLE",		RID_GIMPLE,	D_CONLY },
      { "__PHI",		RID_PHI,	D_CONLY },
      { "__RTL",		RID_RTL,	D_CONLY },
      { "alignas",		RID_ALIGNAS,	D_CXXONLY | D_CXX11 | D_CXXWARN },
      { "alignof",		RID_ALIGNOF,	D_CXXONLY | D_CXX11 | D_CXXWARN },
      { "asm",		RID_ASM,	D_ASM },
      { "auto",		RID_AUTO,	0 },
      { "bool",		RID_BOOL,	D_CXXONLY | D_CXXWARN },
      { "break",		RID_BREAK,	0 },
      { "case",		RID_CASE,	0 },
      { "catch",		RID_CATCH,	D_CXX_OBJC | D_CXXWARN },
      { "char",		RID_CHAR,	0 },
      { "char8_t",		RID_CHAR8,	D_CXX_CHAR8_T_FLAGS | D_CXXWARN },
      { "char16_t",		RID_CHAR16,	D_CXXONLY | D_CXX11 | D_CXXWARN },
      { "char32_t",		RID_CHAR32,	D_CXXONLY | D_CXX11 | D_CXXWARN },
      { "class",		RID_CLASS,	D_CXX_OBJC | D_CXXWARN },
      { "const",		RID_CONST,	0 },
      { "consteval",	RID_CONSTEVAL,	D_CXXONLY | D_CXX20 | D_CXXWARN },
      { "constexpr",	RID_CONSTEXPR,	D_CXXONLY | D_CXX11 | D_CXXWARN },
      { "constinit",	RID_CONSTINIT,	D_CXXONLY | D_CXX20 | D_CXXWARN },
      { "const_cast",	RID_CONSTCAST,	D_CXXONLY | D_CXXWARN },
      { "continue",		RID_CONTINUE,	0 },
      { "decltype",         RID_DECLTYPE,   D_CXXONLY | D_CXX11 | D_CXXWARN },
      { "default",		RID_DEFAULT,	0 },
      { "delete",		RID_DELETE,	D_CXXONLY | D_CXXWARN },
      { "do",		RID_DO,		0 },
      { "double",		RID_DOUBLE,	0 },
      { "dynamic_cast",	RID_DYNCAST,	D_CXXONLY | D_CXXWARN },
      { "else",		RID_ELSE,	0 },
      { "enum",		RID_ENUM,	0 },
      { "explicit",		RID_EXPLICIT,	D_CXXONLY | D_CXXWARN },
      { "export",		RID_EXPORT,	D_CXXONLY | D_CXXWARN },
      { "extern",		RID_EXTERN,	0 },
      { "false",		RID_FALSE,	D_CXXONLY | D_CXXWARN },
      { "float",		RID_FLOAT,	0 },
      { "for",		RID_FOR,	0 },
      { "friend",		RID_FRIEND,	D_CXXONLY | D_CXXWARN },
      { "goto",		RID_GOTO,	0 },
      { "if",		RID_IF,		0 },
      { "inline",		RID_INLINE,	D_EXT89 },
      { "int",		RID_INT,	0 },
      { "long",		RID_LONG,	0 },
      { "mutable",		RID_MUTABLE,	D_CXXONLY | D_CXXWARN },
      { "namespace",	RID_NAMESPACE,	D_CXXONLY | D_CXXWARN },
      { "new",		RID_NEW,	D_CXXONLY | D_CXXWARN },
      { "noexcept",		RID_NOEXCEPT,	D_CXXONLY | D_CXX11 | D_CXXWARN },
      { "nullptr",		RID_NULLPTR,	D_CXXONLY | D_CXX11 | D_CXXWARN },
      { "operator",		RID_OPERATOR,	D_CXXONLY | D_CXXWARN },
      { "private",		RID_PRIVATE,	D_CXX_OBJC | D_CXXWARN },
      { "protected",	RID_PROTECTED,	D_CXX_OBJC | D_CXXWARN },
      { "public",		RID_PUBLIC,	D_CXX_OBJC | D_CXXWARN },
      { "register",		RID_REGISTER,	0 },
      { "reinterpret_cast",	RID_REINTCAST,	D_CXXONLY | D_CXXWARN },
      { "restrict",		RID_RESTRICT,	D_CONLY | D_C99 },
      { "return",		RID_RETURN,	0 },
      { "short",		RID_SHORT,	0 },
      { "signed",		RID_SIGNED,	0 },
      { "sizeof",		RID_SIZEOF,	0 },
      { "static",		RID_STATIC,	0 },
      { "static_assert",    RID_STATIC_ASSERT, D_CXXONLY | D_CXX11 | D_CXXWARN },
      { "static_cast",	RID_STATCAST,	D_CXXONLY | D_CXXWARN },
      { "struct",		RID_STRUCT,	0 },
      { "switch",		RID_SWITCH,	0 },
      { "template",		RID_TEMPLATE,	D_CXXONLY | D_CXXWARN },
      { "this",		RID_THIS,	D_CXXONLY | D_CXXWARN },
      { "thread_local",	RID_THREAD,	D_CXXONLY | D_CXX11 | D_CXXWARN },
      { "throw",		RID_THROW,	D_CXX_OBJC | D_CXXWARN },
      { "true",		RID_TRUE,	D_CXXONLY | D_CXXWARN },
      { "try",		RID_TRY,	D_CXX_OBJC | D_CXXWARN },
      { "typedef",		RID_TYPEDEF,	0 },
      { "typename",		RID_TYPENAME,	D_CXXONLY | D_CXXWARN },
      { "typeid",		RID_TYPEID,	D_CXXONLY | D_CXXWARN },
      { "typeof",		RID_TYPEOF,	D_ASM | D_EXT },
      { "union",		RID_UNION,	0 },
      { "unsigned",		RID_UNSIGNED,	0 },
      { "using",		RID_USING,	D_CXXONLY | D_CXXWARN },
      { "virtual",		RID_VIRTUAL,	D_CXXONLY | D_CXXWARN },
      { "void",		RID_VOID,	0 },
      { "volatile",		RID_VOLATILE,	0 },
      { "wchar_t",		RID_WCHAR,	D_CXXONLY },
      { "while",		RID_WHILE,	0 },
      { "__is_assignable", RID_IS_ASSIGNABLE, D_CXXONLY },
      { "__is_constructible", RID_IS_CONSTRUCTIBLE, D_CXXONLY },
      { "__is_nothrow_assignable", RID_IS_NOTHROW_ASSIGNABLE, D_CXXONLY },
      { "__is_nothrow_constructible", RID_IS_NOTHROW_CONSTRUCTIBLE, D_CXXONLY },
    
      /* C++ transactional memory.  */
      { "synchronized",	RID_SYNCHRONIZED, D_CXX_OBJC | D_TRANSMEM },
      { "atomic_noexcept",	RID_ATOMIC_NOEXCEPT, D_CXXONLY | D_TRANSMEM },
      { "atomic_cancel",	RID_ATOMIC_CANCEL, D_CXXONLY | D_TRANSMEM },
      { "atomic_commit",	RID_TRANSACTION_ATOMIC, D_CXXONLY | D_TRANSMEM },
    
      /* Concepts-related keywords */
      { "concept",		RID_CONCEPT,	D_CXX_CONCEPTS_FLAGS | D_CXXWARN },
      { "requires", 	RID_REQUIRES,	D_CXX_CONCEPTS_FLAGS | D_CXXWARN },
    
      /* Modules-related keywords, these are internal unspellable tokens,
         created by the preprocessor.  */
      { "module ",		RID__MODULE,	D_CXX_MODULES_FLAGS | D_CXXWARN },
      { "import ",		RID__IMPORT,	D_CXX_MODULES_FLAGS | D_CXXWARN },
      { "export ",		RID__EXPORT,	D_CXX_MODULES_FLAGS | D_CXXWARN },
    
      /* Coroutines-related keywords */
      { "co_await",		RID_CO_AWAIT,	D_CXX_COROUTINES_FLAGS | D_CXXWARN },
      { "co_yield",		RID_CO_YIELD,	D_CXX_COROUTINES_FLAGS | D_CXXWARN },
      { "co_return", 	RID_CO_RETURN,	D_CXX_COROUTINES_FLAGS | D_CXXWARN },
    
      /* These Objective-C keywords are recognized only immediately after
         an '@'.  */
      { "compatibility_alias", RID_AT_ALIAS,	D_OBJC },
      { "defs",		RID_AT_DEFS,		D_OBJC },
      { "encode",		RID_AT_ENCODE,		D_OBJC },
      { "end",		RID_AT_END,		D_OBJC },
      { "implementation",	RID_AT_IMPLEMENTATION,	D_OBJC },
      { "interface",	RID_AT_INTERFACE,	D_OBJC },
      { "protocol",		RID_AT_PROTOCOL,	D_OBJC },
      { "selector",		RID_AT_SELECTOR,	D_OBJC },
      { "finally",		RID_AT_FINALLY,		D_OBJC },
      { "optional",		RID_AT_OPTIONAL,	D_OBJC },
      { "required",		RID_AT_REQUIRED,	D_OBJC },
      { "property",		RID_AT_PROPERTY,	D_OBJC },
      { "package",		RID_AT_PACKAGE,		D_OBJC },
      { "synthesize",	RID_AT_SYNTHESIZE,	D_OBJC },
      { "dynamic",		RID_AT_DYNAMIC,		D_OBJC },
      /* These are recognized only in protocol-qualifier context
         (see above) */
      { "bycopy",		RID_BYCOPY,		D_OBJC },
      { "byref",		RID_BYREF,		D_OBJC },
      { "in",		RID_IN,			D_OBJC },
      { "inout",		RID_INOUT,		D_OBJC },
      { "oneway",		RID_ONEWAY,		D_OBJC },
      { "out",		RID_OUT,		D_OBJC },
      /* These are recognized inside a property attribute list */
      { "assign",		RID_ASSIGN,		D_OBJC },
      { "atomic",		RID_PROPATOMIC,		D_OBJC },
      { "copy",		RID_COPY,		D_OBJC },
      { "getter",		RID_GETTER,		D_OBJC },
      { "nonatomic",	RID_NONATOMIC,		D_OBJC },
      { "readonly",		RID_READONLY,		D_OBJC },
      { "readwrite",	RID_READWRITE,		D_OBJC },
      { "retain",		RID_RETAIN,		D_OBJC },
      { "setter",		RID_SETTER,		D_OBJC },
      /* These are Objective C implementation of nullability, accepted only in
         specific contexts.  */
      { "null_unspecified", RID_NULL_UNSPECIFIED,	D_OBJC },
      { "nullable",		RID_NULLABLE,		D_OBJC },
      { "nonnull",		RID_NONNULL,		D_OBJC },
      { "null_resettable",	RID_NULL_RESETTABLE,	D_OBJC },
    };
    
  9. 我看到代码里的这个宏感到不能理解,随后才发现这个是printf的一个format我从来没有用过
    
    #define FPUTS_SPACES(file, space_count, string) \
      fprintf (file, "%*s" string, space_count, " ");
    
    *The width is not specified in the format string, but as an additional integer value argument preceding the argument that has to be formatted.
  10. 我怀疑我的程序失败是我无法理解这个typeof这个所谓的keyword的缘故,我现在才意识到这个是c语言的keyword,那么我的程序在对付c/c++混合的情况下是无能为力的,难道我要把c语言的语法也加入吗?

一月十二日 等待变化等待机会

  1. 去标准委员会的c语言工作小组下载一些官方文档,这个也可以说是一种补偿因为很多的概念实际上是从那里继承来的,因为c++要兼容c语言。比如这篇关于国际化的文档,其中的很多概念是我一直不清楚的,比如execution/source character set后者要求能够表达c语言代码里的那些代码字符,前者在后者基础上比如控制符结束符等等,这些的前提是在某一个locale的语言环境下,比如你在GB2312下的宽字符满足这些条件就能够编程了,char/wchar_t前者定义就是一个byte,后者有可能也是一个或者多个的区别以及和wide character/multibyte它使用wchar_t所以有可能超过一个byte,至于multibyte则是顾名思义了的区别,那么默认locale和locale "C"呢?两者是一回事都是最最基本要实现的locale这里通过实际的例子进一步夯实一个观念就是locale究竟是什么?直观的说就是在设定之后相对应的printf/strftime/isalpha之类的函数会自动使用locale设定的关于字符集小数点金额时间等等的形式,而不需要任何代码的改变。当然我们这里假定的是设定locale函数setlocale选取的参数是LC_ALL这里面有很多的繁杂的东西比如古老的多字节编码的问题,这个我实在是没有兴趣因为估计主要是日文之类的编码,而另一个提到的iso646.h的问题的来源我现在大概明白它是一个政治问题,据说是欧洲有很多老设备要兼容。反正很无聊的东西。
  2. 我在标准委员会网站看到好几份的c语言的工作草稿,关于keyword的变迁让人迷惑,似乎最新版的去除了很多的特别关键字,我看到的最后一版似乎只保留了_Noreturn,那么对应的GCC里定义的当然要更多也包含了历史上的那些消失的关键字,这个实在是很烦人的。我究竟要采用那个版本的呢?
  3. 这篇关于encoding的论文也许值得读一下。

一月十三日 等待变化等待机会

  1. 我之前没有注意到github默认是使用branch名字main结果搞出了笑话了。我需要把我本地的分支重新命名为main才行:
    
    git remote add origin https://github.com/nickhuang99/isoc.git
    git branch -M main
    git push -u origin main
    
    这个就算是练习吧,我意外的发现居然c语言的语法也会有歧义?为什么?

一月十四日 等待变化等待机会

  1. 我做这个练习的一个目的是想看看是否大师前辈的机巧是如何做到c语言只有一个dangling-else的歧义的,因为我发现我按照标准语法是做不到的,问题不在于那些众多的opt的问题,或者说它们是问题,但是你无法解决,最后我看到了一个解决办法就知道这个是无解的,比如大师解决typedef-name这个identifier的同义词的问题,它会和其他的declaration-specifiers合起来产生Reduce-Shift-conflict,但是大师的解决方法是直接在lexer里强制约定字母大写来绕开这个问题,这个仅仅是为了教学方便而已,所以,我不打算再看其他的歧义问题了,因为它们不是能够解决的问题。而且c语言的歧义也是语法固有的总归有三四十个。
  2. 经过几个月的艰苦的徒劳的努力证实了一个基本的尽人皆知的事实:歧义不能不解决,合并并不代表就消除了内存占用的问题,最终还是会内存耗尽的。
  3. 我现在倾向于认为DMS的一个核心是GLR的实现与优化,那位高人前辈在博文里就说过世人选择compiler-compiler的标准居然是是否容易下载安装,这个是多么荒谬的标准啊,流行的就是好的?这个领域是核心的技术,没有这方面的改进是不可能的。wiki里说
    A crucial optimization allows sharing of common prefixes and suffixes of these stacks, which constrains the overall search space and memory usage required to parse input text. The complex structures that arise from this improvement make the search graph a directed acyclic graph (with additional restrictions on the "depths" of various nodes), rather than a tree.
    我怀疑bison还没有实现这个,否则我为什么会耗尽内存?如果merge发生了为什么栈没有合并?难道内存耗尽不是因为这个吗?DMS有太多不足为外人道也的机密。这个方向是不可能的吧?可是他们又宣称只花了相比于GCC/clang几十甚至几百分之一的人力时间就做到了一个惊人的成果?
  4. 讲座里这个惊人的例子: 为什么swap不需要std::也能编译成功?这个是我含在嘴里说不出来的三个字母:ADL!
  5. 这个代码的神奇在哪里?
    
    int ary[]={1,2,3,4,5,3,5,4,2,2,1};
    std::set unique{std::begin(ary), std::end(ary)};
    
    这里不小心使用了{}结果是灾难性的,那么这个语法对应的constructor是哪一个呢? 我使用clang的AST输出根本看不出来,那么怎么才能帮助程序员明白自己使用的是什么语法呢? 在我打算进一步debug之前我看到这个成立
    static_assert(__is_same(decltype(std::begin(ary)), int*));
    这个说明了什么呢?所以,这个结论就不奇怪了: 说明根本就没有调用constructor,而是所谓的initializer_list相当于这个意思 所以,我的结论是{}的形式的constructor调用的是所谓的initializer_list的形式的ctor。这个是要特别小心的,我今天早上差一点就要用到这个形式。
  6. 这里的解说很详细但是我还一时看不进去,究竟模板是怎么解析的我一直也不清楚,也许我清楚了就不会尝试bison了,但是DMS不是就可以吗?
  7. 现在GCC的bug都是超级的变态,我根本就看不懂这些代码,只是感觉这个可以作为一个万能的函数?

一月十五日 等待变化等待机会

  1. 一位大师讲解c++17的关键新功能,让我听的神眩目驰,比如我对于这个meta-function怎么使用都一无所知,它并不是只能存在于meta世界里,那样的话它能有多大用?
    
    template<typename T>
    auto get(T t){
    	if constexpr (std::is_pointer<T>::value){
    		return *t;
    	}else{
    		return t;
    	}
    }
    
    这个神奇的地方就在于std::is_pointer返回值是怎么定义的,那个value是一个true_type或者false_type,而它们的共同祖先是一个平淡无奇的integral_constant<bool, true>,这个结构我也看过很多遍了可是我从来没有注意到它的成员函数是怎么定义的:
    
    constexpr operator value_type() const noexcept { return value; }
    constexpr value_type operator()() const noexcept { return value; }
    
    这里的成员变量value指的就是模板参数的第二个true/false,但是关键的是这两个成员函数返回的都是constexpr,那么你能不能直接调用 if (std::is_pointer<T>::value)呢?或者像我一样无知的这么调用if (constexpr std::is_pointer<T>::value)?这两个都不行,为什么?我知道答案一定就在这长长的列表里的一条,可是我不知道是哪一条?
  2. 有时候编译器报怨你定义的变量没有使用,为了让它闭嘴你可以使用属性[[maybe_unused]][[nodiscard]]则正好相反它强制你必须使用这个函数返回值。
  3. 长久以来我一直不是很清楚要怎么使用std::enable_if总感觉难以理解,今天终于明白它有一个默认的第二个模板参数通常使用它的默认值void,所以如果条件成立的话它是可以返回一个void值的,当然条件不成立是什么都不返回,这样子我们就可以把它作为函数的返回值来使用,遇到条件不成会返回空让函数没有返回值类型导致编译错误。
  4. 如何能够对于一个array的成员求平均数呢?
    1. 首先,我们使用fold expression
    2. 那么我们要怎么传递参数给它呢?我们使用一个小函数来把数组的成员转化一下 注意这里的avg(ary[Is]...);是著名的parameter pack expansion。
    3. 调用函数就很简单的
    4. 这里是原始参数和调用过程 这里我仅仅使用array而不给模板参数是因为c++17的所谓的Class Template Argument Deduction(CTAD)
    感觉这一切都堪称完美!我完全可以省略那个avg函数而合并成这样子
  5. 这中间我遇到一个让我头疼了很久的小错误,这个真的是让人无语! 一开始我看到这样子的错误感到茫然无头序 error: fold of empty expansion over operator+后来看了好久才明白我根本没有传入index_sequence的相关参数怎么能够指望它不是空的呢?这个简直就是低级错误! 17.04.2022过了几个月回头来看这个笔记依然让我大有裨益,因为记忆力的衰退导致我总是对于自己几个月甚至几个星期或者几天前的东西充满了惊奇,同样的,这个可以达到类似于默认参数的效果,需要的是lambda,这个就是我的长进,虽然一点点而已。

一月十六日 等待变化等待机会

  1. 大师关于Deduction Guideline有一个颠覆我认知的例子给我极其深刻的印象。长久以来我一直不知道std::function有什么大用,大师真的是点醒梦中人啊!它是一个万能的套子可以把任何的函数作为变量来使用,而以前这个需要定义复杂难懂的函数类型,而这只是第一层,那么对于类的成员函数呢? 对于这样一个带有重载的成员函数的类我们通常是这么做的 可是如果你想用新的function来替代mem_fn这条路就走不通了,大师的解决方法是深入std名字空间使用Deduction Guideline来指引编译器 然后你就可以像使用mem_fn一样来使用function

一月十七日 等待变化等待机会

  1. 我感觉这个纯粹是一个无意义的想法,就是说你怎么能够遍历你定义的一个enum的所有值,这个是违反常识的因为之所以你定义它为enum,就是为了能够enumerate而不是使用一个循环就能够完成的,那么如果你确实又需要呢?我觉得唯一能够做到的就是使用宏了 换言之,使用宏来定义这个enum,而我故意初始化它们的值使得常规的循环几乎不可能,这种情况其实并非罕见,比如图形上的很多的颜色的enum是使用RGB的组合的值来定义的,你能够简单的使用循环来遍历所有的颜色吗?这个时候只有使用宏。而这里有一个有趣的现象就是这个模板函数foo是一个使用我定义的enum作为模板参数,但是我不小心把两个enum的值定义成一样的了,这个本来是一个错误,但是语法上是允许的,结果导致MondaySunday被重复调用了,这个运行结果就是能够说明这个问题
    
    Today is Monday
    Today is Sunday
    Today is Tuesday
    Wednesday is special because it is wedding day!
    Today is Thursday
    Today is Friday
    Today is Saturday
    Today is Monday
    Today is Sunday
    
    换言之就是我的猜想编译器内部实现了7个函数分别对应于7个enum的值作为模板参数,但是foo<Monday>在编译器看来它对应了两个函数 分别是foo<Monday>foo<Sunday>,这个真的是意外的收获,我一开始以为这个是编译器的特殊性,结果发现clang和GCC在这点上是一样的行为。这里当然也展示了如何输出enum的名字作为字符串的办法,因为c++缺乏所谓的reflection的机制,所以,打印enum自身也通常只能使用宏来做到。 还有另一个有趣的现象就是关于specialization的,就是说我特意对于Wednesday做了特殊化处理,可是假如我现在故意让另一个enum,比如Monday和它一样,结果会如何呢?
    
    #define AllWeekDay \
    	OP(Monday,	12) \
    	OP(Tuesday, 14) \
    	OP(Wednesday, 12)\
    	OP(Thursday, 203)\
    	OP(Friday, 22)\
    	OP(Saturday, 99)\
    	OP(Sunday, 100)
    
    结果也是有趣的现在当你打印foo<Monday>的时候,编译器实际上发现foo<12>有一个更加specialization的处理,结果就调用了foo<Wednesday>这个是结果
    
    Wednesday is special because it is wedding day!
    Today is Tuesday
    Wednesday is special because it is wedding day!
    Today is Thursday
    Today is Friday
    Today is Saturday
    Today is Sunday
    
    所以说无类型的模板参数是很不同寻常的,作为enum来说它似乎是增加了类型的意味,可是在模板内部存储的函数实际上还是数字?
  2. 大师的帖子真的是让人激动!我花了一个下午学习!我厚颜无耻的指出大师的两个小小的不足,一个是关于SFINAE的,我以为标准的做法是把enable_if_t放在函数返回上 当然这里我对于大师的concept做了一个我自认为挺好的简化 这是因为我又一次遇到了>=的问题,这个要非常的小心,因为你以为你省略了中间的空格无所谓,结果编译器就糊涂了。
  3. 事实上确认是否是specialization其实相当的困难,我一时根本想不清楚,后来才意识到大师的做法似乎是唯一的途经,就是你几乎不可能做出一个metafunction来剥离模板参数还原模板类本身,这个似乎语法上也是没有定义的,就是说一个declarator必须要是template-id,而它必须自带模板参数才合法,所以你想返回不带模板参数的模板类本身几乎是不可能的,当然我们可以验证传入的模板参数是否合法,那个不带模板参数的模板类本身是可以作为模板参数传递的,只是你无法输出。比如我费劲心机想让这个成立,结果才意识到这个似乎是不可能的 这个错误信息‘auto’ not allowed in alias declaration起初是让人费解的,但是随后我才意识到作为一个类型来说vector是不能独立存在的,它只能存在于一个模板参数的状态,你想输出是不行的。这个错误信息是GCC的,而clang给出的错误信息就清晰的多了error: use of template template parameter 'Template' requires template arguments

一月十八日 等待变化等待机会

  1. 提了一个bug
  2. 我把大师的的concept改良了一下: 这个克服了大师依赖于constructor来判断的缺陷。
  3. 几乎所有的编译在链接阶段消耗的内存是最大的,即便gcc编译自身的时候也失败了因为我的内存不足。所以,我的并行编译的命令-j $(nproc)太贪心了,估计最多是4就好了。这个和多少个进程编译关系不大是因为我的eclipse占用内存太多了要关掉。我看来是又错了,多进程的确是影响链接,最稳妥的还是单进程。

一月十九日 等待变化等待机会

  1. 关于DR 625背后的原因是什么呢?对于这个问题的答案我并不满意。我自己的理解是模板参数并不是真正意义上的类型,它们是一个类型的参数,作为参数本身并不一定就是一个独立的类型,而作为auto关键字它背后代表着编译器要能够有效的为它推理出类型,我的体会是模板参数并不一定能够单独存在,比如现实里的模板类vector/map如果没有赋予模板参数的话本身就是一个不完整的类型,是无法为其分配内存存储信息的,那么也就不能作为auot能够替代的类型了。
  2. 提交了一个相关的bug
  3. 我对于模板的模板的使用总是不太清楚,这个练习很说明问题的本质,就是模板参数里的模板更多的好像是一个样板,这个听上去就是废话,但是它是一个很好的例子让我明了要怎么声明它:
  4. 这是一个更加实用的例子来创建一个tuple,
    1. 照例我先定义了一个通用的接受三个模板参数的模板,因为integral_constant实际上是一个接受两个模板参数的模板,我想对它进行一个包装,不过也没有想清楚是否必要,难道还有什么其他模板是这个形式吗? 因为这个模板里既有类型也有无类型参数导致这个通用的模板不能使用 如果你想这么实例化TmplTmpl<integral_constant, int, 5>会得到错误的因为这个通用模板只能表达若干个类型参数却无法表达无类型参数。 18.04.2022这里就是更加明确的建立一个不能动摇的观念:模板的parameter pack已经明确了它就是全部类型的或者全部是同一个非类型的,不可能混杂。这个似乎是公理我却曾经模糊。比如对于tuple是可以这么声明出来的 这个当然是人造的无用的模板,但是它说明了你不能对于它来使用模板参数既有类型又有无类型的混搭的,比如integral_constant就不适用。
    2. 然后我们就可以使用一个包装它的模板 这里我们不仅得到类型的别名,还使用它得到一个实例的小函数get,因为我对于别名的别名实例化心里没底。
    3. 这里我们要得到一个tuple 注意这里的parameter pack expansion是针对我们的模板函数的成员函数get得到的,这里当然可以把所有的步骤都包装在一个表达式里,但是这里的语法比较复杂还是一步一步来的好。
    4. 怎么访问tuple成员呢?我经常会把apply和visit混作一团,因为variant和tuple都代表了一个不同类型的组合,在我看来两者很相似,那么为什么我不能使用visit呢?答案当然是显而易见的,variant和tuple的实现机制是风马牛不相及的东西,是我自己头脑混乱才觉得它们相似,作为apply是要对于一个拥有所有tuple成员作为参数的lambda来一次性的操作,而visit则是类似于dispatch机制来分别使用多个lambda来访问,从英文名字就能够明白它们的意义不同了。 这里我对于integral_constant的值使用了fold expression,因为对于一个tuple的所有成员来操作的确是很困难的,你要找到它们的共性,这也是apply的要义。
    这些操作我还是觉得相当的费力,总感觉是脑筋急转弯的游戏,也许对于大师级人物是手到擒来,或者我自己的功力不足,对于普通人都应该能够掌握的平常功夫看的高深莫测,这是不是对于大师们的一种侮辱呢?
  5. 我目前编译GCC可能只有一个选项是必须的就是--disable-multilib因为我目前没有安装32位的库,所以,这个是必须的。
  6. 关于DR 625我的理解还是有偏差的,并不是模板参数不能有auto,而是有限制,比如这个就是允许的 也就是说当你使用auto的时候默认就是必须是无类型的模板参数。我的理解就是这个推理是比较容易实现的吧?
  7. 这里DR711说的deduction实在是非常的微妙,比如
  8. DR1249说的很玄妙。
    
    void f(int i) {
        auto l1 = [i] ()mutable{
            auto l2 = [&i] {
                ++i;    // Well-formed?
            };
        };
    }
    
    如果没有()mutable编译会出错因为i是第一个lambda的常量[i],而这里()mutable如果没有()会有警告说这个feature是c++23才允许的,所以,最好还是加上。
  9. 又提了两个bug,它们有相似之处,都是lambda作为参数传递导致ICE,不过第一个是lambda接受一个lambda作参数我感觉更加复杂一些这个被大侠认定为duplicate,我当时怎么就没有看出来呢?第二个是constructor接受一个lambda,也许根源一样吧?以下这个还是很有意义的代码 It defines a concept IsLambda to restrict template parameter as type of lambda. Then the concept is used to constraint class A to have a lambda as constructor parameter. The ICE happens when a lambda object is passed to constructor of A. 很可惜这段很有用的代码导致GCC的crash,clang13表示毫无压力!

一月二十日 等待变化等待机会

  1. 学习大师的博文,发现了一个小问题
  2. 有一个观念我是模糊的,就是一个lambda是否会被另一个lambda调用,如果它是定义在全局global的是不需要capture的,只有定义在stack的lambda才需要capture。

一月二十一日 等待变化等待机会

  1. 大师在讲解高深的机巧,我却只见树木不见森林 我感觉单单从这个角度来看我的实现要实用一些,因为它不仅仅是vector,任何的嵌套的container都可以结论下的太早了,遇到嵌套的container是一个复杂的类型怎么办呢?比如map的成员是pair,我可以做一个广义的tuple来重载,可是编译器分不清tuple和普通container的区别,我想从是否有成员函数empty来区分,但是这个constraint还真的不好写,写完了我居然不知道怎么用!看这个普通的使用范例也没有头绪。它对于返回值的限制是挺好的解说。
  2. 我花了好大的功夫才明白我这个代码只能在GCC-10.2.0下通过,其实问题更复杂,这个也许是GCC的bug,因为最终编译运行正确的是需要pair也实现因为tuple并不能代表pair。换言之,GCC-10/11寻找模板函数的解决有些问题,clang从一开始就发现了这个问题拒绝编译,但是GCC12居然可以通过编译但是产生代码的时候又反悔说要pair来重载才行,这个行为真的是太怪异了。
    1. 首先我做了之前所说的concept就是防止把普通的container和tuple混淆的限制 这个就是concept的正确的使用方法:concept本身就是一个模板,但是它实例化后不代表一个模板类,你只能把它作为判断来使用,所以,最常见的是使用enable_if_t来作为函数返回值。当它部分实例化的时候才能作为模板参数的类型来限制模板参数,但是我以为模板参数里都不能算作完整的类型,只能说是类型参数或者无类型参数。这里我之所以不允许string类型是因为string本身已经有了和ostream重载操作符<<的实现,不限制的话编译器会报怨歧义。
    2. 我一向认为pair就是一个特殊的tuple,似乎哪里也提到过可以这么看,但是显然的它们不能替代。所以,我只能分别实现它们的操作符<<的重载 这个pair当然简单了。
    3. 但是tuple还真的不容易,我就犯了错。 这个是参考了官网的做法。核心是这个fold expression,但是我欠缺的是怎么做到不打印最后一个逗号(,),这个例子才是经典。另一个值得学习的就是这个不是所谓的binary fold expression,它实际上还是unary fold expression,我曾经试图使用binary总是卡壳,这个做法是很值得思考的,就是说operator是简单的,,那么你可以任意定义一个括号()的表达式其实也可以理解是作为parameter pack expansion作为函数参数的展开因为最外层的括号可以看作是函数调用也就是说(os << val << (++n != sizeof...(Ts) ? ", " : "")), ...纯粹就是最外层的括号的参数的展开,这个理解我感觉比unary fold expression更容易些。
    4. 这里最让我意外的是函数的前置声明,我怀疑是GCC的bug,最终我决定不管如何都把pair和tuple的函数原型前置声明因为container的重载需要用到。这里有一个疑问,我能否把带有enable_if_t的concept也前置声明呢?答案显然是不可能的因为所谓的concept就是在模板实例化的当时来导致编译转向,你分离了声明和实例化根本达不到目的,这是一个用脚趾头都能想到的。
    饿了。
  3. 我现在才明白标准的concept的使用是这样子的 当然我也可以进一步包装这个concept让它变得短小一些 然后照样使用,不过这里我不知道有没有使用auto来简化concept的使用的办法 这个是比使用enable_if_t作为函数返回值的SFINAE要进化的了。
  4. 这里是总结如何使用concept
    1. 第一种是直接在模板参数声明之后就使用requires来修饰限制模板参数
    2. 第二种是在函数声明之后用requires关键字来限制函数 这两者有何区别呢?
  5. 大师的一个例子又一次颠倒了我的认知,模板参数不能使用auto吗? 一开始我还以为这里auto也是限制为非类型参数比如只有int之类可以,但是我又错了: auto是什么时候不能作为模板参数来使用呢?
  6. 这里再次复习一下constexpr的用意,以及官方的user-defined-string-literal的神奇功力。 这个万能的字符转换函数,需要一个判断输入类型是否可以直接转换为string的,这就是一个很好的对于标准to_string的包装
  7. 花了洪荒之力完成了一个我自认为很完整的一个字符串输出万能函数。这个使用constexpr动态判断和使用众多的模板重载类型在我看来有异曲同工之妙,究竟哪个更好真的不太清楚,这个是一个很好的练习,还有缺憾就是unordered_multiset需要完整的一个hash函数还没有完成,这个是我下一步的工作。
  8. 怎么判断一个变量的类型是std::array呢?这个还是一个判断specialization的metafunction,看似简单我却被困了很久 这里的诀窍是什么呢?就是说我们的一般形式就是我们将来使用的形式,需要一个模板类型参数,而我们针对std::varray的特殊化要使用它固有的模板参数就是两个,那么作为特殊化得到返回的true_type
  9. 相类似的是我们怎么判断一个类型是一个initializer_list呢?

一月二十二日 等待变化等待机会

  1. 我昨天花了一天时间做那个万能的字符串化的函数本来不知道有什么用,现在碰到hashable的问题才意识到这个是很有用的一个思路,因为作为std::hash其实就是希望达到一个这样万能的能力,但是当我看到这里才意识到问题不是那么简单的,这里说要达到的五个原则是很明显的,可是随后隐含的要求做到DefaultConstructible, CopyAssignable, Swappable and Destructible,我就没有看出来,这是什么原因呢?Swappable真的有必要吗?

一月二十三日 等待变化等待机会

  1. 现象就是你不能定义这样子的类型unordered_set<unordered_set<int>>,原因吗很简单就是hash(Key)不允许,因为unordered_xxx都是依赖于yigehash函数来实现的,对于普通类型这个hash都是有实现的,但是当你递归定义的时候这个nested的hash函数就不行了。表现出的编译错误是error: use of deleted function ‘std::hash<std::unordered_set<int> >::~hash()’然而具体是如何做到禁止的呢?我曾经天真的以为使用delete关键字来定义,可是允许的是很少数基本类型,你要如何做到否定大多数呢?显然不行。看到/bits/functional_hash.h一行注释 // Private rather than deleted to be non-trivially-copyable.是关于如何通过SFINAE机巧来定义constructor为private来阻止其他类型的特例化的。这个是我的一个小小的实验,基本上是照抄__hash_enum的做法:
    1. 假如我们定义一个类并且它唯一做的就是定义它的operator()函数,那么这个就必然要求它必须要实例化以后才可能被调用。这样子我们就要求它的constructor/destructor必须是完好才能实例化,这个也就为我们的隐藏而不是delete创造的条件。
    2. 通过模板类来实现的好处是我们要强迫使用者必须要要提供模板参数,因为通常的非模板类虽然也有隐藏constructor/destructor的技术,但是我们现在是强制类型区分的,也就是说我们还要克服现代编译器自动推导模板参数的漏洞防止用户浑水摸鱼混过类型检查,所以,我们这么定义 这里我为了展示仅仅包容integral类型所以特地添加了is_convertible来包容。这个是模板的一般形式,真正的威力在特例化部分。
    3. 这个是特例化部分 这个也就是意味着只有整型才可以使用我们这个类的operator(),这里hash函数只是示范,具体做什么是我们的选择,但是关键就是使用SFINAE和隐藏来限制类型比如这个就是非法的 前者明显的类型就是不允许的,后者虽然能够创建实例但是参数类型转化也是不可能的,我之所以多加了一个is_convertible的宽松条件就是因为事实上这个总是能够成立的IntegralOnly<int>{}(12345.123)因为用户会浑水摸鱼或者误用之前的实例来调用不同类型的参数,而函数的参数类型自动转化是不受我们控制的,索性开放能够转化的类型强过掩耳盗铃,因为float/double都能顺利转化为int等等。
  2. 我一直不是很清楚标准为什么增加了一个所谓的scoped enum,现在才明白原来是为了防止古代传统上能够轻易的把c语言的enum转化为int的习惯。比如首先这个转化是非法的
    
    enum class MyEnum{
    	Monday, Tuesday, Wednesday
    };
    static_assert(is_convertible_v<MyEnum, int>);
    
    但是值得注意的是如果去掉class只是声明一个传统意义上的enum的话,这个转化是合法的。当然这里也许要写的更复杂一些来判断一下所谓的underlying_type,但是大概意思就是如此。另外一个优势就是这个scoped enum顾名思义要访问它的成员必须是完整的scoped的访问,比如传统的没有class/struct的enum你是可以直接这么做的
    static_assert(is_enum_v<decltype(Monday)>);
    可是对于scoped enum你必须要加上所谓的scope就是MyEnum::也就是说
    static_assert(is_enum_v<decltype(MyEnum::Monday)>);
    其实这两点都很好理解,如果你把这个所谓的enum class/struct想像为一个另类的class/struct就不足为奇了。这个新feature在实现上几乎毫不费力。
  3. 我本来想让unordered_set/map之类的使用一个我定义的万能hash就是可以把任意的容器的成员挨个hash然后累加得到总的hash,后来一想这个完全无意义,因为unordered_xx这一个家族的出现的意义在于去除繁琐的传统的set/map的弊端提高效率,如果你允许把一个巨大的vector来这样hash而作为排序的基础的话简直就成了笑话,所以,对于unordered的家族的hash函数的选择是要慎重的。当然任意类型都可以hash或者转化为字符串也就是serialization是有意义的。
  4. 然后我以为我了解了其中的真谛,然而一实践就发现完全不是那么回事,比如说我想使用void_t来写一个concept来判断什么样的类型是可以使用std::hash的,结果这个是不对的! 就是说如果我定义这么一个模板函数使用这个concept并不能起到防止非法类型的预防作用 因为我在声明函数类型是没有问题的原因我认为void_t仅仅是检查语法是否有问题,并不是检查是否可以实例化 所以,在实例化后就出现了错误 所以,能否hash的原则不在于模板类型而是能否实例化hash<T>,所以我修改了我的concept 这里的启示就是void_t不是能够对付这种并非类型限制的错误,hash函数故意设计成需要实例化以后才调用它的operator()是很有深意的,我至今还不能领悟。还是需要回到当初如何领悟这个hashable的五大原则
  5. 这里我为了探究什么是hashable的五个原则做了这个concept 这个当然是很不错的,可是我卡在了is_default_constructible是怎么实现的,结果我意外的发现它真的没有那么简单,居然是依靠关键字内部实现的。在gcc/c-family/c-common.cc下定义了{ "__is_constructible", RID_IS_CONSTRUCTIBLE, D_CXXONLY }但是从代码里我还是看不到gcc如何实现的,这个究竟是怎么实现的呢?而stackoverflow上的回答我都看不大懂更不要说判断对错了。我唯一的感觉是这个看似简单的问题实际上非常的复杂,纯粹靠library实现是很不容易的,而编译器内生实现我又没有找到!找到了,是这个函数is_xible_helper相当的复杂,首先剔除void类型抽象类以及成员函数和只读等等,然后是剔除数组等等,然后是令我惊异的看到注解 总而言之,非常非常的复杂!真的想不出如果不是在编译器层面实现,而library层面要复杂多少倍?我一向认为在library层面的实现代码往往要比编译器层面复杂很多,不是吗?比如你如何判断一个类是抽象类?单单这一点让你写SFINAE就够喝一壶的了。
  6. 也就是说__is_constructible__is_same等等都是GCC的内部关键字,这个是标准以外的GCC内部函数一样,它比declval的依赖于SFINAE的实现要来的更直接,但是MSVC++就不买账了,所以还是要使用declval来的更加的portable。我至今对于declval的实现还是抱着崇敬的心情望而生畏敬而远之,因为总是看不太懂,想出这些怪招的大侠都是旷世奇才。 这段12行代码让我再看几遍也是惘然 大师在这个内部实现函数的定义里参数一个是int,一个是long,这个是什么意义呢?大师在逗我玩的吗?我不想再看了。
  7. 最后没有忍住还是做了这个实验才理解一些些:
    
    struct Base{};
    struct Derived:Base{};
    Base b;
    Base& bref=b;
    [[maybe_unused]]Derived& dref=static_cast<Derived&>(bref);
    
    就是说一个继承类的引用如果不做强制转换的话是不能够从基类引用自然得来的,这个从法理上说这个转化是合法的也就是说 static_assert(!is_constructible<Derived&, Base&>::value);,但是据说有的依赖于库的实现就忽略了这一个case。而编译器实现的版本是不会犯这个错误的,我估计declval的版本也不会因为它是SFINAE的机制串线了?declval是不可能创建带参数的。。我对于这里的is_trivially_constructible感到无法理解。

一月二十四日 等待变化等待机会

  1. 有concept的函数的声明的先后顺序不影响执行的结果,也就是说你添加了限制条件的重载会被优先执行和它们声明的顺序无关。 我们声明了两个函数有限制条件的放在后面,那么当我调用的时候函数会找最佳匹配就像是模板特例化一样寻找最接近的那个
    
    hello(5);  // output:  requires hello
    hello(5.5); // output:  no requires hello
    
  2. 当我费劲心机想要做一个万能的hash函数的时候我不得不入侵std名字空间去篡改或者说污染标准库所在的名字空间,这个始终是非常的危险的,但是标准似乎不允许specialization in different namespace,这个建议n3730通过了吗?
  3. 我发现github上有一个c++文档的repo,这个也许很好用吧?其实它只是官方c++委员会的一个部分,这个才是更有用的。这其中我发现draft最有用,你可以获得最新版的c++标准文档,当然我还是非常青睐大侠改造的非常精致的html版本。它的源头是这里,等我有时间再去折腾看看我能不能简单编译。而这里是github上的标准库的官网,这个是非常有用的资源。
  4. 我觉得我一个早上做的这个万能hash还是很有用的。它的核就是侵入std对于hash做一个特例化。 然后我们就可以随心所欲的计算任意数据的hash了,注意到我们甚至可以包含所有的unordered_xxx了!还有就是它甚至包含了tuple!我以为这个是我今天最高的成就! 顺便说一下,之所以能够顺利的调用就是因为满足这个五原则也就是说目前这个是成立的
  5. 前些天回答一个问题的时候其实也是我自己对于学习c++的体会,有时候感觉这个语言真的是太难了,将来真的还有人会去学习吗?昨天看头条上有人在回答报怨大学计算机一年级学习数据结构使用c语言来学习是否合适的问题,相对于c语言或者java之类的语言来说c++实在是太复杂了,也许将来c++的库再完善到以至于做到包罗万象比如包含了所有平台的基础的API然后才可以和java/c#之类的运行在中间层之上的语言来竞争,也就是说那时候c++语言就是一个跨平台的编程语言只要个平台编译器以及标准库实现者做好工作就行了。这是一个多么遥远的未来啊,目前c++标准库的网络系统文件等等是否有必要在系统库再去包装一层呢?也就是说这样子的包装广大系统开发者是否买帐?
  6. 这个是一个我以前笔记的格式的老问题了,这个是我目前的手工的作业方式:
    1. 首先使用chardet来检测文件的编码是否和目标utf-8一致,很多时候我发现我以前是使用GB2312编码的,于是需要改变编码。
    2. 转换编码需要使用iconv -f GB2312 -t UTF-8 input.html -o /tmp/output.html
    3. 经常在编码转换发现一些非法编码,iconv给出了一个文件的偏移,这个很麻烦,我只好使用dd来查看非法字符: dd if=input.html bs=1 skip=pos-1 count=1
    我一直想写一个脚本来做这个,但是确实不太可靠,很多非法字符也许是当时编辑器的bug或者是当时错误设定了locale造成的混乱,总之我只有手工修改。
  7. 有时候我连一个基本的问题也想不到解决的办法,如果你不想文件列表出现某些文件名字,难道你不能使用regex来表达吗?我感觉我可能没有真正的理解glob的含义就是regex的表达吧?比如我不想看到文件名开头字母是f的.html文件,难道我没有想到这么做吗?for i in $(ls -d [^f]*.html); do chardet "$i"| grep GB2312; done 不过这个做法我始终发现无法很好的处理文件名包含空格的情况,似乎还是find好一些吧?

一月二十五日 等待变化等待机会

  1. 这是一篇相当不错的c++博文,其实看似简单其实很深刻,它暴露了我很多的无知,其实也不能说无知,因为很多的东西实际上是c++20才引入的,作为以前浏览过的古代代码就显得我似乎挂一漏万,因为并不是我的记忆出了偏差而是以前这些都不曾有。且不说高级的contains成员函数减少了内存的分配,它如何实现string和const char*之间的比较这个最基础的问题我都不是很清楚。
    1. 首先,我们要复习一下string的成员函数compare比较字符串的实现机制,这个不是c++20的东西是远古时代就实现了,但是我今天看代码还是吃惊自己为什么感到意外。难道我一直都是以为它直接调用strcmp?很多在我看来似乎多此一举的行为应该是大有深意。首先获得string的长度,这个无可厚非而且是现成的。然后要获得字符串的长度这一点我就感到意外了,因为最终很可能都是要调用最原始的strncmp,那么string可能中间包含null字符所以是不能直接调用原始的strcmp或者strncmp的,只能先比较两者的长度看有没有快速结果。
    2. 那么计算字符串的长度难道有什么花头吗?难道不是直接调用strlen之类的吗?显然我幼稚了,难道没有想到很多字符串是所谓的编译期的常字符串吗?这里肯定有大量的优化做法,这也就是我看到两三个判断这类情况的语句。这个内部的__constant_string_p在做什么我也看不大懂。总之最后调用我期待的__builtin_strlen这个时候代码里出现了一些令人恐怖的什么INTERCEPTOR我完全不知道这个是干什么的。最后我猜测这些额外的代码是所谓的阿三就是asan就是辅助性的和代码逻辑无关的,这样我才放心了,总不至于我连strlen都看不懂吧?可是实际上这个所谓的__builtin_strlen我既查不到文档看来是太基本的小函数,也不是简单的对于strlen的包装。就在我绝望的用简单的grep无法找到它的定义的时候我只能认为这个是GCC的又一个复杂的宏的结果,然后这个帖子救了我。原来都是定义在builtins.def,总之我实在是看不下去那些复杂的宏了。总而言之即便是计算字符串的长度就是一个复杂的过程,中间夹杂着我附带的asan的代码看的人头昏脑胀。
    3. 当我们有了两者的长度之后我们是否可以直接调用strncmp之类的呢?当然不是为什么不直接先比较长度呢?这个当然是要做的,可是然后呢?不c++有自己的字符串定长比较函数这个在最后调用strncmp之前还有很多的步骤,又是针对常量字符串这里也许包含了数组字符类型做优化处理,总之,我是太天真的以为字符串比较就是当年伊甸园时期随手写的strncmp。
    4. 如果你以为你使用strncmp的结果就可以返回你就又是天真了,难道你没有想到传入的长度本身就是所谓的非法的吗?就是超出整型的上下限。
    一个早上就在看一个基本的字符串比较啊。
  2. 偏离了主题太多了。我们要怎么能够利用string和const char*的比较呢?因为如果set/map的key是string的话我们使用原来c++98年代的定义的话就会把const char*先转化为string来做key-comp,这个是set/map模板默认的comp模板参数的定义,直到某一天默认的less<Key>可以被我们使用less<void>来显性的制定,这样子的less的特殊化允许它使用两个不同的模板参数,也就是说我这个代码 这是一个典型的判断weak-ordering的相等的做法,可是我一开始想避免声明left/right两个functor,而是共用,结果就暴露了它们实际上是不可替代的,因为参数的不一致要求模板参数也不一样所以两个functor是完全不同的类型,这里就间接证明了less特例化为void模板参数的时候使用两个不同的模板类型。 这里让我印象深刻的是 这个是显而易见的吗?我以前一直以为没有模板参数是模板特殊化的标志,难道空就等于void吗?
  3. 后面的关于那个神秘的using is_transparent = int;我的代码里看不到这个定义,我看到的是typedef __is_transparent is_transparent;而这个所谓的__is_transparent根本就是一个纯粹作为标志量的空结构。这个是怎么使用的呢?我还没有找到我估计又是一个复杂的SFINAE的范例吧?
  4. 这个使用不是那么容易的,首先,如果你要利用它作为set/map的函数contains,本身这个搜索的比较的类型要事先被定义了,然后你要显性的调用less的void作为模板参数,这一切似乎都不是那么普遍。仿佛在回答我的幻想一样,大师有一个专门指出我的企图的例子,你不可能一方面使用一个key建立你的结构却又要用另一个key来搜索。但是我始终无法理解这个transparent的作用,而且似乎我的代码也和MSVC不太一样?

一月二十六日 等待变化等待机会

  1. 大多数时候我们没有必要把一个数组或者向量里的成员作为参数传递给一个函数,这个是违反常识和直觉的,因为数组或者向量不是tuple,它的成员是同一个类型的,有什么必要浪费函数调用的参数传递的资源来这么做呢?你本来可以传递一个或者两个参数现在传递和数组或者vector成员数目一样的参数这是在倒退到远古汇编还没有发明的时代吗?可是假如我的题目是:给定一个数组或者vector要求每一个元素都是它后面成员的总和。
    
    {1,2,3,4,5,6,7,8,9,10}
    {55,54,52,49,45,40,34,27,19,10}
    
    这个问题你有什么简单的做法呢?动态规划?也许一个递归就可以解决了,可是我一开始就想偏了,我总想利用fold expression来做些什么。
    1. 首先,这个纯粹是象牙塔里的自娱自乐毫无实际意义,命名可以两行递归就完成的。 比如使用index的递归可以轻易的也适用于数组比如这么recurse(v, 0);我要是不说,谁都以为v是一个数组。
      
      int recurse(vector<int>& v, size_t i){
      	if (i==v.size()){
      		return 0;
      	}else{
      		v[i]+=recurse(v, i+1);
      		return v[i];
      	}
      }
      
      如果非要使用iterator的话其实很多其他的容器也可以轻易的使用,同样的针对这样子的调用recurse(v, v.begin());这里的v可以是很多其他类型的容器,比如list?
      
      int recurse(auto& v, auto it){
      	if (it==v.end()){
      		return 0;
      	}else{
      		*it+=recurse(v, it+1);
      		return *it;
      	}
      }
      
      这里我有意使用auto就是为了其他容器也可以使用。
    2. 对于递归函数的声明很多时候是一个烦人的事情,我很想使用lambda因为这个简单的递归我不想开放给别人用因为很危险。lambda如何递归呢?我的记忆力太差了还是要看笔记。 lambda的递归通常是要借助于function的声明,但是这样子的话我就无法借助声明参数都是auto的便利了,也就是说我声明的递归函数必须是参数类型明确的。 注意到这里的捕捉必须是引用的,通常lambda是不应该传递引用的因为它们是独一无二的,可是这里我们已经包装了一层function它是有状态的,所以要递归就必须传递引用[&f],而它的调用方式还是类似于传统的递归函数的f(v, v.begin());
    3. 能不能不用递归呢?因为这个操作实际上就是fold expression的操作,于是我开始想把所有的数组成员传递给fold expression,但是这里有一个致命的前提就是说vector的长度必须是编译期已知的,否则你是无法把任意长度的vector的成员作为参数传递给另一个函数的,也就是说我的使用的数组是这么定义的: 我使用了一个常数VectSize才能实现下面的做法 这个是一个标准的拆解vector的成员并把它们作为参数来调用一个unary left fold的做法。 注意这里我必须要知道vector的定长才能做到
    4. 但是我一向不喜欢模板函数的定义,因为它们不能像lambda那样定义在我的函数内部因为我这个小函数并不适合于暴露给别人看,我想使用lambda来定义 这样子就好看多了,我不用担心被别人看到而愤怒了,如果我能把我的逻辑代码都限制在一个函数本身那肯定是最大限度的实现局域化了,这个调用就有些尴尬了,因为lambda的模板参数传递实在是不好看
  2. 我针对这么一个简单的递归想的太多了,不过当我突发奇想要把这个计算的顺序颠倒一下,我却遇到了很大的困难。也就是说这个是我要达到的结果
    
    {1,2,3,4,5,6,7,8,9,10}
    {1,3,6,10,15,21,28,36,45,55}
    
    1. 当然首先我的vector还是一样的使用iota初始化
    2. 然后我绞尽脑汁想要使用right-fold expression结果根本不是我想要的,这个时候我才意识到我是不可能简单通过使用fold right得到,唯一的简单的办法是我得到一个reverse_index_seq。也就是说{9,8,7,6,5,4,3,2,1,0},这个看起来很容易的工作耗费了我一个早上也不成功。首先我尝试递归函数,结果根本算法就不对。不要说算法是错误的因为我实际上只是做了一个shift根本不是颠倒顺序,其次,编译器对于模板函数是产生无数个实例结果编译期就stackoverflow了。
    3. 然后在反复失败后不得已找到以前的笔记,但是没有真正掌握原理的后果就是我还是不知道怎么做,直到看了stackoverlow的原来的帖子才再一次的感叹大侠的巧妙,你根本就不用去实现这个函数,说到底它就是一个类型,只要你能够在编译期得到它的类型就可以创建它的实例,这一切看上去不少的代码但是都是在编译期完成的,对于运行期来说几乎没有开销
    4. 然后就是照样的使用我的lambda创建一个fold expression
    从这里看出来原本递归非常简单的算法搞得我头昏脑胀。
  3. 如果给定任意一个sequence,如何将它反转,我写了一个无比复杂的实现,感觉这个实在是太笨拙了,应该有更加简洁的做法,而且我的功力不够无法使用递归继承的meta programming的正道,结果写了一个递归函数显得非常的不入流。
    1. 首先,我笨拙的实现了一个给定任意index获得当前sequence的index处的元素,写的很笨拙 这个是测试以及示范 注意这里的index是从0开始的。
    2. 然后根据传入的index获得一系列sequence的元素再返回新的sequence 这里要特别注意如何获得get_reverse_sequence_helper的返回值类型的,这里实际上不是一个fold expression,准确的说是函数参数的parameter pack的展开。注意没有()或者,,这个说明它是函数参数的展开形式。
    3. 这个是测试
  4. 又提交了一个bug。这个是关于函数返回值就是trailing return type的问题。因为如果不是GCC的这个bug,我完全可以把我的函数定义为仅仅有一个返回值的纯粹的声明而已,根本不需要定义函数体。 也就是说我根本不在乎函数的实际运行,因为我仅仅想得到它的返回值类型,这个和元编程里面使用结构定义返回类型是异曲同工的,可惜GCC有bug,现在clang13和MSVC都没有问题。

一月二十七日 等待变化等待机会

  1. 你知道要怎么实现lambda的递归呢?我以前以为只有function一条路,没有想到还有另一个简单的做法,就是把lambda自己作为参数传递给自己!这个是原始的博文,作者做了详细的分析,我感激涕零,不用再自己瞎折腾了。大家不约而同的都用fib做例子
    
    auto lambda=[](int n, auto& lam){
    	if (n<2){
    		return n;
    	}else{
    		return lam(n-1, lam)+lam(n-2, lam);
    	}
    };
    
  2. 我发现我对于lambda的理解依旧是浅薄的,究竟unevaluated-context的实质含义是什么?为什么这个判断是错误的?也就是说以下是成立的 为什么我声明了lambda的类型却不被承认呢?难道
    static_assert( ! is_same_v<decltype([](){}), decltype([](){})>);
    这个就是对的吗?我去下载原始的标准提案,并没有看明白什么。其中说了动机,但是我并不明确理解似乎就是原来c++14/17之类的不允许lambda作为unevaluated operand,也就是说decltype/sizeof之类都不允许出现lambda。作者说这个最早在clang上实现的仅仅就把一行代码注释就成了,而GCC我觉得简直就是一团浆糊,这样子的类似的检查林林总总不胜其烦。我敢断定GCC没有明天了,因为未来似乎是注定clang的天下,而它现在需要做的就是提高一些效率。
  3. 这里有一篇非常好的文章谈论什么是unevaluated operand我要好好读一下。首先这个是我目前的一点感悟,就是unevaluated operand并不代表它不是一个全新的声明,或者说 static_assert(sizeof([](){})==sizeof([](){}));并不妨碍它实际上测量了两个独立的不同类型的lambda,不一定有实例,但是两者类型不同。 我一开始感到这个无法接受 但是随后也释然了,因为每一个lambda都是独一无二的,所以,你每次取的lambda都是独一无二的,所以,这个是合理的。

    然后我看到了作者的这个惊人的例子,我都震惊的无语了!

    这里的decltype(item.func(), void())在做什么?原来它实现了类似于concept/constraint的功能!这个简直是不可思议,有用吗?当然了!因为如果你随便使用一个模板参数并不支持成员函数func的呼叫的话,编译期就报错了,感觉错误信息比concept不差!

    接下来这个例子也是颠倒我的认知的,原来noexcept是这么使用的啊!

    这里的noexcept据说也是一个unevaluated context吧?我们只有在这个成员函数没有抛出异常我们才做抛出异常的,这个enable_if_t作为返回值等于是一种没有concept的concept!真的是太棒了!这篇文章太长了,我随后再度吧。
  4. 另一方面我对于+[](){}有了新的认识,它实际上是一个函数指针类型
  5. 回到lambda的一个基本问题:Why do lambdas capture variables at all?我读了以后羞愧不已,全局变量谁能拦得住?需要你capture吗?为什么需要capture局域变量?因为它们的生命周期限制了它们的使用啊,所以,你要小心或者引用或者拷贝,前置增加它的引用次数防止过早消灭,后者复制一份即便消灭也无妨,这一系列都是针对局域变量的啊!这么基本的道理我居然不懂?
  6. 我发现网上有太多的好的资料了,比如这个How to Avoid Template Type Deduction in C++似乎就不错。我还没有时间去读,实在是太多了,人的精力太有限了。

    这里是标准的换key的代码,可是有什么问题呢?

    问题出在你使用的时候如果是字符串的话编译器自动推理类型认为它还是字符串 这里的"two"还是导致类型的不匹配。作者的高超的做法使用这个声明 const Keytype_identity_t<Key>& oldKey,

    原来这个做法也是标准的做法,因为如果要阻止template argument deduction,其中的一个方法是把类型包含在nested scope里,这个也就是identity的用意。

  7. 前两天的代码也可以这么写,似乎少写了几行:
    1. 首先iota一个vector
    2. 然后是比较紧凑的写法使用fold expression 本质上没有什么区别,不过就是少写一两行代码。
    3. 打印输出同样是少写一行代码
  8. 实际上我以前一直不理解+[](){}这里的+类型是什么意思,现在我理解这个就是一个指针类型,因为static_assert(sizeof(+[](){})==sizeof(void*));所以,我一开始幻想定义这个类型的lambda来作为递归capture是不可能的,因为它并没有实例化,根本没有定义。

一月二十八日 等待变化等待机会

  1. 看到头条上的小题目,看能不能把代码写的更短呢?
    1. 这个是要求把一个整数的每个数字放在一个数组里,也就是说给定一个数字12345要把它放在一个五个成员的数组里成为1,2,3,4,5,我偷懒使用vector,数组可能要费一些脑子计算index。 如果是改用数组的话需要计算每个数字的位置
    2. 这个看上去简单,但是无法写的好看,就是数一个字符串里单词最长的,原来题目只是要求返回长度,我修改要求返回那个单词,当然也就包括长度了。 这个返回等长度的第一个 这个使用range来做返回等长度的最后一个 所以,对于相同的输入char const* Input="hello I am a newby but how is yesterday and a9letters "; 返回的结果分别是
      
      longest word is length of 9 of: yesterday
      longest word is: a9letters of size of 9
      
      出题者有意考验编程不允许使用任何库函数,不知道这个算不算是库呢? 这个显然不合题义了。
    3. 出题者要求遍历一个目录下的所有文件找出最大的文件的大小,要求假定只有这么三个函数可以用,就是is_file,is_folder,file_size。这个我们可以很容易的把文件系统的directory_entry作为handle来包装一下: 然后就是一个简单的递归,我额外的返回文件名以便验证一下:
    4. 分解质因数,我觉得写的太冗长了,但是似乎没有什么好办法。 643居然是质数,这个让我吃惊了一下:123456=2*2*2*2*2*2*3*643;
    5. merge两个已经排序的数组是不是太陈词滥调了?能不能把代码写的更短一些呢?这个代码有重大问题!!! 这里居然会有问题,我第一次遇到-Wsequence-point warning,原来这个c语言程序员的天经地义的不存在了?比如这个是非法的,或者是未定义的UB 天哪!这个都会翻船!原因是什么呢?因为这里的[]看似人畜无害的数组访问其实是array的函数,那么根据这个原则,函数是一个sequence point而这个赋值=到底发生在这个函数之后的话那么copy[i]的值就不同了,所以,我被迫写成这个古怪的东西:
      
      while (i<5)
      (copy[i]=left[i] , i++);
      
      所以上面的代码要修改成这样子 这里必须使用size_t index=l+r;固定下目标下标,原因自然是?:操作符是一个sequence point,这里面的修改是要完成之后才会返回值的。同样的array的operator[]是一个函数,它也是一个sequence point,其中的下标变化是要完成才会返回给赋值操作的。这个看来是对于c程序员的一个重大的考验。 测试程序是这样子 结果是
      
      7,13,19,25,31,37,43,49,55,61,67,73,79,85,91,
      2,4,8,16,32,64,128,256,512,1024,
      2,4,7,8,13,16,19,25,31,32,37,43,49,55,61,64,67,73,79,85,91,128,256,512,1024,
      
  2. 既然lambdak可以作为模板参数,那么我们定义一个模板来调用我们的lambda不是一个很好的包装吗?
    1. 比如我们有一个简单的lambda
    2. 第一种方式我们想纯粹用类型来传递这样子我们的包装就是一个无状态的模板类
    3. 另一个方式是我们把lambda实例当作参数传递给ctor,并保存一个引用。 怎么调用它呢?一开始我随手使用Stateful(lambda)();编译器报怨我返回一个函数,原来这里会有歧义,于是只可使用{},也就是Stateful{lambda}();
  3. 这位大侠实际上简化了我的问题。
  4. 我觉得我可以简化这个问题,但是就是卡在那里。我的问题其实很简单就是给定两个向量,我要把它们像是向量相乘一样的扩展为一个向量。 比如{1,2,3}X{4,5}==>{1,4,5,2,4,5,3,4,5}。看上去简单,可是做起来很费劲。
    1. 第一步先做一个标量乘以向量的小函数 因为这个是元编程,计算的都是类型,根本没有必要定义实现,只要能够指明返回值类型就足够了,这个就是meta function的意义。
    2. 接下去我要实现一个合并若干个向量的小函数,其实问题就在这里,我无法定义这么一个sequence of sequence的模板参数,所以只能依赖于fold expression并重载了sequence的operator <<
    3. 这个就是调用fold expression的小函数,它利用了上面定义的operator<<
    4. 这个是调用的入口,它接受两个sequence,然后调用combine函数的参数expandOne作为它的参数利用了parameter pack expansion传递参数。
    5. 这个是验证
    怎么才能简化这个过程呢?因为我不喜欢使用fold expression,它是唯一很难使用trailing return type来定义meta function的障碍。
  5. 感觉这个note非常的误导我的理解,难道这个参数不能用在requires里吗? 我理解了大侠的意思,可以再简化这个问题,可是我很怀疑这个理由
  6. 最后我发现似乎clang也有问题,好像不一致 在我看来,两个函数要么都通过要么都不通过,怎么会一个可以一个不可以呢?

一月二十九日 等待变化等待机会

  1. 遇到一个典型的例子就是如果你不明确书写你的trailing return type而是依赖于所谓的deduction guide的话,编译器往往给出错误的答案。比如我想打印输出index_sequence就重载了ostream的operator<<,可是我如果没有给编译器明确提示我的返回值是ostream的引用&的话->ostream&,编译器会爆出这样子的错误:error: use of deleted function ...编译器认为我的返回值return os;返回的是一个const引用的对象拷贝。而库里明确禁止了这个错误的发生: basic_ostream(const basic_ostream&) = delete;就是copy constructor被禁止了,你始终只是使用同一个cout之类的ostream对象,这个是很重要的技术类似于强迫实现了类似于singlton的意思。
  2. 我想继续写一个比较好看的sequence的代码,我发现继续尝试使用模板参数模板的parameter pack很困难,要怎么表达一个无数的不定长的sequence呢? 我尝试了模板发现很可能不是不可能的,唯一的希望是使用auto,因为它是variadic template的更好的选择,你也不知道怎么表达那就交给编译器来想吧。所以,这个是一个老老实实的版本,我一开始发现我只有重载多个参数个数然后递归才能做到把多个sequence链接起来。而且折磨我很久的是我居然忘记了要前置声明,也许我脑海里把auto和模板函数混为一谈,后者是不在乎声明顺序的因为最后模板匹配的时候编译器会把所有的模板函数排序匹配,所以不存在声明的顺序,而auto实际上有点像是普通函数还是需要顺序的。 但是当我想把这些函数都变成纯粹的只有返回值类型定义的虚幻函数(我找不出恰当的名词来说明)它们的时候遇到了困难,因为这里实际上是有递归定义的问题的,最终我只能放弃。 不过令人欣慰的是我实际上也达成了我的目的因为我调用这个函数的时候仅仅需要它的返回值类型说明我的计算都是发生在编译期的,所以,与其删除函数的定义引起警告说inline function used but not defined来的更好。 结果{1,2,3,4,5,6,7,8,9,}期间我得到了一个小小的启示,就是声明函数前置声明的时候如果没有添加trailing return type的时候而我在定义函数的时候又添加了的话有可能引起ambiguity的问题,比如我重载的这个函数 在编译器看起来有trailing return type和没有的声明是两个函数。
  3. 如果对于一个没有捕捉的lambda来说, 它基本上是可以满足以下这些条件的,也许这个可以作为判断is_lambda的concept,但是这个太复杂了。 对于它的成员函数j就是那个operator()()要怎么获得呢?如果获得了,我们可以怎么调用它呢? 然后我会得到和直接调用lambda();相似的结果,但是如果lambda有捕捉的话,首先它的is_default_constructible_v不成立,而且也不能直接创建同类型的实例。但是我们可以使用copy的方法: 如果我们的lambda有参数的话is_invocable_v,is_invocable_r_v也要相应的加上参数类型,但是我们有lambda的实例直接调用lam(name);不就行了为什么要使用invoke呢?这个纯粹是没有意义。
  4. 这个是两个sequence的两两组合,这个好像不是我要的
  5. 往往最简单的道路我却没有想到,比如这个是获得两两组队的 总共有四种组合方式 这个是第三种 最后一种

一月三十日 等待变化等待机会

  1. 一篇文章很有趣,我还没有完全领悟。
  2. 受到启发改进之前的颠倒数列的做法,结果发现了GCC10.2/11.2的问题,这个只有在最新的12.x才能编译通过,感觉一直在测试编译器的能力边界: 所以经过改进的颠倒数列的代码是这样子的 这个是测试
  3. 也许将来有一天很多人编程就是在如godbolt.org上进行了? 去除一个元素是非常的困难的,因为这个有点像是namespace一旦加入你想把它去掉似乎是没有办法的。 这个是一个去除数列里特定元素的示范,很烦人的,你等于是重建了一个比它小的和比它大的数列合并而成。它有什么用呢?它可作为去除tuple里元素的工具。 这个就是进一步的示范
    1. 我们想要做的就是去除tuple里的某一个元素
    2. 具体怎么实现呢?如果我们有一个去除了元素所在Index的数列那就是很简单的了,这个就是做法
    3. 所以,关键是怎么取的这样一个数列 想法就是取得比这个index大的和比它小的数列的合并,这里有一个特殊情况就是我们需要去除的是最大的那个就直接产生了。
    4. 这个是一个小函数把当前数列都加一个数 合并数列是否也需要写一个函数呢? 很多时候我想跳过这类假函数可以直接写一个using的alias比如 这个是不是看上去更好看呢?
  4. 找到一个很好的c++加拿大的社区
  5. 交换数列里两个元素容易吗?我看更麻烦。
    1. 首先,我们需要的结果就是 就是说要把类似于参数里某两个交换位置。怎么实现呢?
    2. 这个是我们的实现函数
    3. 其实如果我们能够生成一个数列那么从tuple里重新排列交换就如同探囊取物一般容易
    4. 怎么生成这个数列才是核心的问题。 所以问题就抽象化为给定两个在Count范围的下标First,Second,如何产生新的数列而这两个下标位置交换。
    5. 而今我谓昆仑,不要这高,不要这多雪。安得倚天抽宝剑,把汝裁为三截?一截遗欧,一截赠美,一截还东国。太平世界,环球同此凉热。
      这个算法来自于主席的诗词不妨称之为润之算法吧?分成三段然后把两个元素交换位置再重新连接,好麻烦的操作。
    6. 这是用到的小函数,把一个数添加到数列的结尾 这个是把两个数列合并
  6. 这个是标准的点乘的实现,我现在还领悟不了fold expression是怎么运用的。这里的...+(someop)是unary left fold,更加的符合习惯从左到右,对于这个向量来说区别不大,但是其他就不一定了。 这个是测试cout<<dot_product(tuple{1,2,3}, tuple{4,5,6})<<endl;答案是32。 这里我学到了如何不适用index_sequence来实现点乘的方式,就是两次使用apply,这个是很有难度的 这个要好好体会,不容易,因为最终还要使用fold expression。
  7. 这里的大侠写的代码很复杂据说能够计算结构的成员变量数目,可是我还没有看懂。
  8. 这是一个非常好的经典的例子就是如何应对constructor的问题的,这个应该就是make_unique的实现的代码吧?或者。。。 这里的关键是很多类有各种各样的ctor,如何才能正确的选择呢?比如vector<string>你要如何创建呢?很明显的是curly braced,可是你不一定总是能够判断清楚吧?比如vector<size_t> 这个是非常的要命的模糊,而且make_unique的实现还不一定有以上我们的这个扩展,而且即使有了也无法避免这个错误模糊 也就是说尽量使用new来创建unique_ptr,因为针对有的ctor的多参数的歧义性make_unique是无能为力的。 这里我犯了一个认识的错误,就是说我听说constexpr会自动把假的的选择支扔掉,于是我就在代码里写上了不可能的部分的代码希望编译器能够忽略掉,但是这个是针对模板才有的,对于实参违反语法的是不能接受的。 这个编译是不可能通过的因为在错误的选择支上的语法是不支持的,这个并不是模板替换参数静悄悄的扔掉,它是代码的一部分。我已经反复观察到,如果没有使用constexpr的话模板函数会生成大量的模板实例函数,比如我是递归的模板函数很有可能直接就把编译器的栈爆掉了,所以,能够使用constexpr的尽量使用。
  9. 关于fold expression我总是忘记,已经反反复复很多遍还是记不住 第一种是binary left fold因为cout作为init是在左边,而且操作符<<出现了两次,所以是binary。而第二种是unary right fold,因为第一它的操作符,出现了一次,它的...在右边,这个是最难记忆的,为什么是右边,总之unary看pack的位置,因为pack是ts是在左边,所以就是unary left fold。相应的left就是left先操作,right就是right 先操作,unary就是说没有初始化元素参与,只有pack成员之间的binary operation,而binary就是有init参与,binary left fold意思是init在最左边并且第一个参与操作相当于初始化或者说init出现在第一个操作的左边,而binary right fold说明init出现在最右边,也是第一个执行但是它是给最右边的元素赋值。感觉我还是记不住。

一月三十一日 等待变化等待机会

  1. 看到头条上有人出问题不知是什么语言估计是Haskell之类的函数式编程语言吧,就是说关于fold函数的极限,给定一个数组,一个初始值,一个累加的方法如何求数组的和,并且要求在遇到数组的数为一定值的时候返回,这个对于List之类的编程确实是难写,因为它们一条语句的背后可能是语言本身几百条语句的实现,你享受了别人的劳动成果就要受别人实现的限制,这个是自然的法则。对于c++你要做的工作还真不少,但是有一点就是几乎是无所不能的。
    1. 首先这个是我们像要做到的结果,就是一个数组一个初始化为100的变量,然后我只能想到使用异常来跳出fold expression。
    2. 此外这里因为需要使用fold expression隐含的表示我们是类似于元编程,所以数组长度是已知的,我可以使用普通数组然后依赖sizeof(ary)/sizeof(ary[0]来得到数组长度,不过为了偷懒这里使用array就额外增加了一个长度的常数,不过不影响结果 我不得不定义一个index_sequence来做到使用fold expression,感觉对于可以使用循环解决问题的使用递归效率肯定是比较低的,这个也许没有运行期的开销,但是编译期肯定比普通循环复杂的多。这里我使用的是operator,这个是我目前唯一比较有把握的,因为操作元是一个特定的lambda这个似乎是唯一的办法。
    3. 累加的办法当然是很简单的了,碰到特定数值抛出异常捕捉再获得累加的结果。
  2. 我的知识漏洞很大,几乎每走一步都看得见,比如什么是aggregate type呢?这个是和aggregate initialization紧密相关的,而我始终对于c++新的最基本的初始化或者说{}不明所以然原因就是在此。它首先是数组,这里对于我根深蒂固的c++98的观念对于char ary[]="hello";虽然每天再用但是始终有顾虑,因为远古时代曾经被迫一个字符一个字符的初始化让我记忆犹新以至于c++11都这么多年了我还是不太肯定。看到这个我首先想到的居然是unsigned char是否类型不匹配这种想法。unsigned char b[3] = "abc";数组长度啊!literal有sign和unsigned的问题吗?只有const的问题吧?那么其次对于一个非数组的类型要成为符合aggregate initialization的标准是很严格的,一律不得有ctor或者什么private/protected之类的成员,也不得有继承之类的,最重要的是不得有虚拟方法(virtual),这一点应该能够想得到,其实就是在计算sizeof的结构的时候必须是简单的成员累加而虚拟方法表是类的隐藏的成员会占用空间的。
  3. 我之前就已经打算花整段的时间来认认真真的攻克这篇博文的难题,因为我知道这个是非常难的,我需要打点精神集中注意力才能完成阅读,结果还是遇到了无数的坑,学习到了很多基本的机巧和方法,收益良多!
    1. 还是从基本概念开始,我们能够做什么?首先是aggregate的概念,就是我们只能对于is_aggregate_v是真的类型做出判断,所以不妨设定一个概念 我这里突发奇想添加了禁止数组的限制因为这个太复杂了主要是针对机构而对于数组我们可以轻易的获得它的长度,所以,它们都属于aggregate,但是我防止用户滥用,这里命名以后再说吧,还是叫做aggregate吧。
    2. 我以为我们碰到的第一个也是最核心的问题就是怎么知道一个aggregate-initialization-enable的结构有多少个成员呢?元编程的威力在于利用编译器给我们答案,这个是最最核心的一个难关,如果这个想不到这个问题就无解,这里可以是最核心的代码了,我们使用一个concept来实验! 不要小看这三行代码我还被它折腾了很久!首先这里是巧妙的利用编译器直接告诉我们这个结构可以使用多少个类型的参数来初始化,我们虽然不知道是什么类型有多少个,但是我们知道了使用declval这个神器可以做到!这里要强调一点不用担心这个参数个数,它一定是所有的成员的个数,因为aggregatable已经定义了它必须是而且没有自定义的ctor,不可能有第二种可能。
    3. 可是我们并不知道这些成员都是什么类型有多少个,首先要解决类型问题,能不能做一个万能类型呢?这个是本题的第二个难点,这一点想不到,也是无解。 我本来也是小瞧了它以为std::any不就可以吗?完全不是一回事,我们需要的是一个可以替代任何类型的类型,这个存在吗?不存在!你怎么可能创造一个代表任意类型的类型呢?你不是上帝不可能,就算是上帝也不可能,上帝不可能创造一个甚至可以代表它自己的类型那还得了吗?但是注意到declval<Args>()并不说明它一定就是ctor,难道不能是conversion operator吗?两者在外人看起来是一样的啊?就好像六耳猕猴和孙悟空看起来是一模一样的,所以这才是核心的关键,就是创建一个万能的conversion operator可以代表任何的类型。神仙不能代表任何事物但是神仙可以变化为任何事物。
    4. 但是问题又来了,如果我们需要不确定数量的参数我们怎么创建它们呢?又要怎么命名呢?这里又是一个关键想不到这里这个题目还是无解。使用序列号!怎么做呢?模板,可是模板类实际上是不同的类型啊,我们要创建的是很多类吗?不我们只创建一个any类型不过它的conversion operator是模板函数,这就是一个关键。想不到这一层后面的代码就没法写了。 如果我们随手把any定义为模板类的话以下这个是不成立的 看上去两者没有什么大不了的,可是这里using会导致编译器报错error: ‘auto’ not allowed in alias declaration这个是我最近接触到的一个相关的bug的问题,就是这里模板参数是不能使用auto的,虽然看上去代码没有出现,可是实际上我们依赖于编译器自己推理类型,编译器是无法对付不确定类型的别名的,总之,使用单一类型any仅仅让成员函数是模板函数是可以减轻编译器的困难的,因为最困难的是地址分配的问题一旦结构的大小确定了就没有问题了,至于说模板函数那个是之后匹配的问题了。
    5. 大侠开始进行惊险的特技表演了,首先是怎样利用以上我们来做一个使用任意数量的不确定类型来表达创建aggregate的呢? 这里就是典型的模板特例化的威力,这里我一度糊涂认为Indexes和下面特例的模板参数size_t...Is不匹配,后来才恍然大悟模板参数不是看声明部分而是看模板实例化的参数个数是否匹配,在参数部分仅仅是声明的参数而已,因为真正的类型是index_sequence<Is...>对应的类型是Indexes!所以标准的元函数的返回值大都是使用继承来传递的,我们在特例化使用返回结果来继承,而这里广泛应用parameter pack expansion就不是这里的重点了,如果这个看不懂的话就根本没有可能来体会这里最重要的诀窍了。
    6. 接下去当然就是怎么调用这个元函数了。 其实这里我想记录下来我犯的一个幼稚的错误,就是在定义一个concept的时候我把模板参数使用另一个concept来限制类型结果编译器反复报错我却不知所云:error: a concept cannot be constrained这里说的是说作为concept不能依赖于另外一个concept传入模板参数,这里的AnotherConcept T是被禁止的!我以为没有这样子的概念还以为可以更加的美观,结果花了好久才明白。
    7. 怎么数数呢?这个是需要技巧的,这里又是一个典型的做法 我们在一般形式的模板定义了递归继承,但是递归如何终止呢?就是在特殊化部分做到改变继承作为输出,而这里的参数的选择就很重要了,额外的bool类型作为一般形式的递归是给继承递归类使用的,而我们特殊化不要在模板参数里出现就表示我们有特例化的可能了,这个如何才能算是特殊化是我始终感到困难的难点,应该就是在模板参数里减少就算吧?或者模板参数具体化也是吧?而改变继承对象作为返回参数是关键。
    8. 其实很多时候你就算明白怎么实现而对于怎么调用也有些发憷,似乎很简单,可是我为什么想不出呢?主要还是自己写不出实现自然想不到怎么应用它: 我一开始一晃眼以为是一个alias的重定义比如 这么做为什么不行呢?我原本觉得这个不也是常见的做法吗?我完全可以单独使用 那么为什么不可以给它一个别名呢?这里的原因是很深奥的,感觉这个又是SFINAE里的精微奥妙的地方,也许就是这个c++20/17的新特点模板变量被声明为constexpr就是说constexpr size_t aggregate_field_number_v在模板里对于constexpr不成立的分支都会被静悄悄的抛弃,才大幅度的减少了那些无用甚至有害的模板实例,也就自然而然舍弃了那些不合语法的模板实例,否则也会像我定义的这个using一样爆出错误,因为我们只返回我们需要的结果。这里的道理是很艰深的。
    9. 大部分的实现都可以被隐藏在一个detail的namespace里以便不要污染环境。还有一点就是我曾经错误的把concept当作一个函数来用使用了不存在的::value结果爆出的错误让我摸不着头脑说什么concept id出现在nested namespace之类的,现在才理解。
    10. 作者还有改进不使用这种普通递归对于我来说太高级了,我今天就只能消化这么多了。
    看完这个感觉太多了,要好好学习。最后一点是一个小问题困扰我好久就是模板和==连在一起造成的解析错误,比如我没有留空格结果 结果还是clang爆出的错误比较亲民:error: a space is required between a right angle bracket and an equals sign (use '> =') 把代码重新默写了一遍也算是小测验吧。

二月一日 等待变化等待机会

  1. 这里的这位大侠说明的这种函数作为参数是一个很普遍的高阶函数递归调用的模型。就是说f(f(i))这种形式,这个限制条件还是有的就是参数必须和返回值类型一样,但是有一点小小的问题就是invoke_result_t对于参数为空的情况并不允许你使用void替代,结果就导致你要做一个小小的判断,这个小小的技巧是我反复在元函数里看到的,但是自己学着使用还是费了一番周折,所谓看人做千遍不如自己做一遍,反复看并不能帮助你学到这个技巧。尽管如此的小。>结果我反而被误导了,忘记了我们必须接受一个参数才可以递归!
    1. 目的就是定义一个concept它能够反映具有这种immediate recursionable的函数,也就是说 is_same_v<Arg, invoke_result_t<Fun,Arg>>
    2. 所以,我先定义一个允许特殊化的帮助函数 这个是通常的有一个不为空的参数的情况,注意到我预留了参数是void的default参数的情况,这个是为了做特殊化处理的。另一个注意到is_invocable_v足为测试,这一点我一开始忽略了,打算放在concept里再做结果又遇到了相似的问题,因为invocable和invoke_result是相似的对于空参数是不使用void替代的,所以,必须特例化
    3. 这个是特殊化 这里这么做的原因纯粹是因为invoke_result_t的设计而导致的。这里我们的特殊化是当我们使用默认参数的情况,所以说特殊化是一个对我始终比较难以掌握的领域,怎么才算呢?
    4. 然后就是定义concept了
  2. 我重新定义就是一个返回值和参数一样的函数才有可能immediate递归 可是立刻打脸,我对于我的想法感到完全的失败,这个看似简单的问题怎么会错了呢?比如我使用一个递归的lambda来测试
    
    int counter=0;
    auto lam=[&](auto& self)->void{
    	if (5>=counter++){
    		cout<<counter<<",";
    		self(self);
    	}
    };
    
    这里让我震惊的是如果我不定义返回值为->void,编译器就报错,而这个完全颠倒了我对于递归lambda的印象。
  3. 经过一番摸索我才意识到lambda如果要定型的话返回值是一个很重要的方面,所以可以递归调用的lambda是这样定义的 但是虽然我可以这样子递归调用lambda了:lam(lam); ,可是它真的满足我定义的concept吗?显然不行,因为c/c++都不允许函数返回另一个函数类型,这个是明确定义了的,你可以返回一个functor比如lambda但是不能是函数类型。 然后我花了好久才意识到我的错误这个判断为什么不成立呢?原来参数类型是引用啊!
    
    static_assert(is_invocable_r_v<decltype(lam), decltype(lam), decltype(lam)&>);
    
  4. 感觉脑子很乱完全混乱了,可以递归和高阶函数是两个不同的概念,所谓可以递归的条件很简单,只要函数本身可以作为参数就可以了,大概是 至于说函数的返回值类型完全无关。而这位大侠说的复变函数或者说高阶函数是可以多次递归调用并传递参数的,也就是说返回值是和参数类型一样。
  5. 网上有一个如何获得函数的参数类型的帖子很让人受启发,我把它改进了以下。 其实这个是本来很容易想到的,可是我的脑袋就是不开窍,总也想不出来,这里就是最经典的模板特例化的技巧,原作者不支持类的成员函数指针,我增加了一个特例化。
  6. 费尽心力花了快一天才完成这个arguments函数,它是一个几乎万能的模板函数能够获得普通自由函数,类的成员函数指针,或者是functor以及lambda作为参数的所对应的函数参数列表。
    1. 它的核心还是通过这个signature的元函数来获得参数 这个是它的primary的形式
    2. 这个是针对自由函数的特例化这里也许需要针对各种specifier比如extern之类的吧?我没有时间测试了
    3. 这里是针对成员函数指针,包括了lambda/functor,它们有分const和非const,也许还有其他的,我没有时间测试了。
    4. 这里是我花了好长时间才明白的简单的道理,就是要针对三种不同情况来写concept,你不能把他们写成逻辑的组合,因为它们彼此都是不成立的,这个不是所谓的constexpr你可以取舍,最后模板都会失败,所以要分别写
    5. 这里就是分别针对不同的concept的函数,相当于不同模板特例化一样,我说只是好像,应该说constraint和concept是函数签名的一部分,所以,才能分开。 而且注意到我必须分别重载了传入参数,因为有些情况比如自由函数它只能以引用的形式吧?总之重载是很必要的。
    这个是真的把我累坏了。我的答案发布了。

二月二日 等待变化等待机会

  1. boost prf的实现还是有些不太好懂,不过c++17的实现我大概能够猜出来,这个基本上人人都可以做到,只不过确实不好看,实际上就是依靠structured binding,比如这个就是很简单的做法 这个做法只能是使用大量的宏来预先生成所有数量的模板,至于说c++14的两种实现方法就很复杂我还看不到在哪里。
  2. 既然lambda是一个functor,那么它自然也可以被继承了,这个还有什么必要怀疑的呢?那么是匿名的类也是类。
  3. 这个应该都是相应的小函数,基本上稍稍思考就可以得到的,比如给定一个模板类的变量如何知道它的模板参数呢? 就比如
  4. 这一篇关于concept的介绍还是很全面的,我快速浏览了一下相当于复习一下。
  5. 一年多前让我看这些hana/mpl我会觉得好似天书奇谭一样,现在我感觉好多了,只是快速浏览了一下子。感觉我的屠龙手段有了很大的进步。
  6. 这里的疑团让我百思不得其解。
    1. 我有一个callback,它是一个lambda是用来为我处理数据的 它很明白的是一个函数指针
    2. 然后我有一个函数来接受数据以及这个callback来做数据处理 可是让人惊讶的是编译器认为我的callback是数据而不是函数指针!这里我几乎可以肯定是又一个GCC的bug,不过这个要这么做,要使用函数指针 而这个是让人不能苟同的因为怎么可能指针是数据呢?
    3. 与之相对应的普通函数指针不会发生这个问题。比如这个是一个普通的函数指针
    4. 我们定义一个相似的处理函数使用这个函数指针
    决定提交这个bug
  7. 这个是一个基本的常识:也就是说这个lambda的成员函数operator()是一个模板函数,这个和整个lambda是一个模板类是完全不同的,我至今还想不出要怎么使用模板lambda。它可以直接使用,可是我无法把它做参数传递,因为类型不知道要怎么确定。
  8. 我居然不知道这个是c++20被删除的新constraint的东西!就是所谓的错误error: return-type-requirement is not a type-constraint,这个是一个我写的concept 注意返回值不能用类型,而是要用bool类型来表达。这个是我定义的lambda及其回调函数使用concept

二月三日 等待变化等待机会

  1. 昨天太忙了一直在关注程序的语法部分,今天可以总结一下如何使用模板lambda,以及怎么使用。比如我们需要一个接受一定参数类型和一定返回值的lambda想把它作为参数来传递进行数据处理,那么你只能声明参数类型是可赋值的那种lambda,我不知道它的官方名称如何,反正就是加+号使得lambda实际上成为函数指针,只不过它似乎不是成员函数指针而是和普通程序指针一样吧?这一点我以前一直不明白现在才看清楚这里面学问大了 这个是怎么做到的呢?绝对不是简单的输出operator()因为这个是成员函数指针需要类的this指针做额外参数,而且类型根本不兼容,除非是把this作为默认指针参数来输出的,就好像那些functor添加参数输出的?换言之,我昨天声明的这个模板lambda根本不是模板,而是一个模板函数指针!注意不是类型而是指针变量! 我有意使用小写突出它变量的本质!这一点我昨天直到现在都是一个模糊的认识。既然它就是一个指针变量那么它的类型才是昨天问题的关键。 所以,现在看到这个就不应该感到惊讶了 就是说using Lambda创建了一个模板函数指针的类型,而auto lambda则是声明了一个模板函数指针的变量并且初始化了它。所以,这个是一个很好的作为类似于concept的做法,你可以使用这个模板函数指针来限制传入的参数这个和 究竟哪一个形式更好呢?我觉得如果我预计要传入的参数是lambda的话似乎使用lambda的那个形式更好一些因为也许用户不太敢确定参数是否接受lambda,总之形式相近就是一个提示参数类型吧?不要忘了虽然参数是可赋值的lambda形式,但是它就是一个函数指针类型但是很奇妙的是它能够接受lambda,比如 这里我传入的lambda是和函数指针类型兼容的,这一点是让我比较吃惊的,也正是lambda的伟大之处!函数指针还是lambda两者的类型兼容是编译器做到的吗?我为什么惊叹呢?因为普通的functor做不到 为什么lambda可以做到呢?
  2. 大侠的patch来的好快,我还看不太懂,大概就是token-hack之类的吧,可能就是把longest-match改变吧?
  3. 我对于这个所谓的user-defined-conversion-operator一直不得要领。直到看了这个帖子才有些明白。 就是说实际上lambda说到底是一个functor,那么如果能够把它转为纯粹的函数指针势必要去除this的羁绊,可是这个要求lambda是一个无状态的functor,所以不能有capture。那么这个是怎么做到的呢? 这个说明了什么呢?说明你的lambda如果没有captuer,那么你定义的函数指针变量是可以直接使用lambda的,但是这个绝对不是你想像的那么简单,因为lambda和普通的函数指针是不兼容的,所以,这个代码的实现是必要的,普通的也许就是cast去掉那个lambda的this指针。

二月四日 等待变化等待机会

  1. 我曾经反反复复思考都无法理解lambda,尤其是遇到之前的那些神奇的转换更是让我感到lambda是如此的神秘,而大众似乎很少谈论神秘面纱下的真容。渐渐的我看到了一丝曙光。
    1. 这篇关于神秘的+的霸王帖让我初窥端倪!这个引述非常重要:

      13.6 Built-in operators [over.built]

      8 For every type T there exist candidate operator functions of the form

          T* operator+(T*);

      我需要找到它的出处。而这之前有很多的概念需要补齐。
    2. 首先什么是built-in operator?原则上应该是指这些大家耳熟能详的司空见惯的操作符,只是我从来没有把它们冠名为built-in而已。
      1. Subscripting
      2. Function call
      3. Explicit type conversion (functional notation)
      4. Class member access
      5. Increment and decrement
      6. Dynamic cast
      7. Type identification
      8. Static cast
      9. Reinterpret cast
      10. Const cast
      11. Unary operators
      12. Increment and decrement
      13. Await
      14. Sizeof
      15. Alignof
      16. noexcept operator
      17. New
      18. Delete
      19. Explicit type conversion (cast notation)
      20. Pointer-to-member operators
      21. Multiplicative operators
      22. Additive operators
      23. Shift operators
      24. Three-way comparison operator
      25. Relational operators
      26. Equality operators
      27. Bitwise AND operator
      28. Bitwise exclusive OR operator
      29. Bitwise inclusive OR operator
      30. Logical AND operator
      31. Logical OR operator
      32. Conditional operator
      33. Yielding a value
      34. Throwing an exception
      35. Assignment and compound assignment operators
      36. Comma operator
      这里的几乎每一个都有宏篇大论相伴随,很多平常人认为平淡无奇天天都在用的操作符其实也是非常的深奥,而有些生僻的我几乎从来也没有用过。
    3. 那么它们都有一个大家都熟知的原则就是只要操作元里有任何一个是类或者enum那么它就可以被重载,这一点很重要因为对于Unary expressions来说只有一个操作元,那么它的重载就是无任何歧义的,显然二元操作符重载肯定是有两个以上的可能的,只有一元操作符才是干净的,所以,这么做是有原因的。而一元操作符是非常有限的资源* & + - ! ~。我们要选择谁下手呢?很明显的原因是+-因为其他三个都已经名花有主了,大众有约定成俗的重载习惯其实应该还有理由这个*是不能重载的。我依稀记得&虽然允许重载但是会给大家造成混乱难道取地址操作你要hack吗?而对于!传统意义上是判断是否初始化方面的重载,所以,我才说只有+-可以动脑筋。
    4. 是不是任何操作元是类或者enum就能自动导致解决为用户定义的重载呢?未必。这里有一个可能的用户自定义转换有可能截胡
      ...an operand has a class type that has a user-defined conversion to a non-class type appropriate for the operator, or when an operand has an enumeration type that can be converted to a type appropriate for the operator.
    5. 终于找到了这个著名candidate operator的论断!我怎么从来没有听说过呢?
      T* operator+(T*);
      06.02.2022不过根据我现在的体会这个帖子说的应该是这个形式
      
      T operator+(T);
      T operator-(T);
      
      因为我们重载的是类的operator+/-并不是指针类型。至少对于lambda也是这样子的。
    6. 再深入理解之前我们先看看大家司空见惯的行为:
      
      auto lambda=[](const string& str, int age){
      	return str+ " is: " +to_string(age)+" years old!\n";
      };
      auto fptr=lambda; // what is type of "fptr"?
      
      这里发生了什么?这个fptr是什么?不出所料它是另一个lambda的变量,这个是毋庸置疑的,你可以像调用lambda一样调用。 这个奇怪吗?当然不,可是这个呢? 这个是否让人感到意外呢?为什么一个类如lambda可以自由的转换为一个函数指针呢?这个是普通的functor的本能吗?显然不可能的。 它是无论如何不可能实现FunPtr funPtr=Functor{};
    7. 那么我自己实现这个Functor的conversion operator如何? 可是有问题因为这个是死循环
      
      gccTest.cpp:503:27: warning: infinite recursion detected [-Winfinite-recursion]
        503 |                 constexpr operator FunPtr ()const noexcept{
            |                           ^~~~~~~~
      gccTest.cpp:504:32: note: recursive call
        504 |                         return static_cast(*this);
            |                                ^~~~~~~~~~~~~~~~~~~~~~~~~~
      
      不要以为仅仅是警告,因为它会crash的,如果我们把constexpr改成consteval
      
      constexprconsteval operator FunPtr ()const noexcept{
      	return static_cast<FunPtr>(*this);
      }
      
      这个问题就暴露无疑了因为编译根本就是不可能的。 怎么办?看样子lambda内部实现这个是不简单的!
    我还是出去看看雪再说吧???
  2. 我无法使用static_cast因为i这个最终是再次调用conversion operator,所以,这个只能是编译器内部实现:lmabda.cc:maybe_add_lambda_conv_op, 这里的conversion实现是怎样的呢?另一个小问题我终于有了眉目,就是说如果我使用static_cast实际上是要再次调用conversion operator,所以,最原始的使用uintptr_t来获取内部函数地址,但是这里要怎么bind member function呢?感觉还是有些悬。。。同样的这里的原始的例子是可以处理lambda的,而我自己传递functor就失败的根本原因就在于原来的代码实际上是利用了lambda的conversion operator,所以它可以把lambda直接cast为函数指针,而我的functor因为并没有内置的实现是无法做到的,我去返回this指针是不行的,因为之所以functor能够作为函数指针使用是因为有了conversion,否则你需要调用它的operator()才行,但是这样子就要bind了。

二月五日 等待变化等待机会

  1. 所以,我昨天一定是太心急了,今天打算一步一步来解释。
    1. 首先,什么是unary operator +?它应该是这样子的。 这里我要让它返回Functor的指针 ,而不是函数指针,这个很容易做到,而且我不需要任何参数,因为对于unary operator我还不知道要怎么传递参数。
    2. 它是怎么运作的呢?
    3. 那么对于lambda来说它的operator+要返回的是函数指针,获取函数指针类型不是什么问题,我已经做了很多了
    4. 所以我想像中的应该是这样子 所以,我现在不知道的是要怎么获得一个函数指针而且要bind this指针的那种。
    5. 我们从头来复习一下mem_fn我一度以为这个是被c++20淘汰了的,实际上是升级了,但是我感觉有些糊涂怎么使用,一开始我无意中使用模板参数自动推导结果失败了: 为什么mem_fn ptr(...)这样子编译不行呢?我猜想是模板参数推导有问题,因为我要明确的指定模板参数还是有一点困难的。这一点我不是很确定似乎这里有什么深层次的冲突也许我把类成员变量搞混了?还是说它没有默认的ctor?但是不管怎样这条路走不通因为 需要不定数量的参数placeholder,这个是做不到的。很麻烦的。
    6. 闹了一个大笑话而自己在无助的瞎碰!function的模板参数是什么?是函数的signature,结果我传递了一个函数指针类型导致编译器始终在报怨incomplete type而我却不明所以然。比如我的眼睛很迟钝无法看到这个明显的错误std::function<void(*)()> funTest;,我当然可以指责编译器的错误信息模糊,但是这个都是我自己的问题。
  2. 几乎是耗费了穷荒之力实现了一个样板,就是模拟lambda实现conversion operator的原理吧。
    1. 目标:首先我们有一个functor,我们希望它能够通过operator +返回一个自由函数指针使其能够直接调用这个functor的原本的operator(),也就是说对于这样子的一个functor: 我们希望它能够实现这样子的功能 而这个ptr的类型是一个函数指针,也就是FunPtrFunctor::operator()函数签名去除类的成员指针后一模一样。
    2. 这个要怎么做到呢?我们打算使用一个static的成员变量来存储这个函数指针变量,然后重载operator +()来返回这个指针变量。
    3. 所以,关键就是这个m_ptr要怎么实现的问题了:
      
      const FunPtr Functor::m_ptr=+[](const string& str, int age)->string{
      	auto ptr=mem_fn(&Functor::operator());
      	return ptr(Functor{}, str, age);
      };
      
      所以,我们利用了lambda可以返回自由函数指针的原理在初始话的时候运用了lamba的operator+,所以,我们就可以在lambda里使用mem_fn来获取Functor的成员函数指针直接调用它的operator(),注意这里我们对于这个Functor的要求和lambda是一样的就是它不能有状态,否则我们没有办法通过传递一个无状态的Functor{}来正确的调用functor的函数的。
    4. 这仅仅是一个范例,其中的lambda的函数参数都是hardcoded,如何计算函数参数等等感觉有可能不行,我前面都可以计算得到但是最后一步卡住了,就暂时先做这个示范吧。好累啊。
  3. 提了一个clang的bug,因为我一开始以为clang也做对了,结果发现只有msvc做对了。
  4. 我想写一些关于lambda的小品文来分享我的心得。

二月六日 等待变化等待机会

  1. 在参数传递parameter pack的时候有一个技巧就是使用实参来传递型参,这个说起来很饶口而且违背常识,实际上是很简单的东西,就是类似于index_sequence的作用,你为了能够传递一个数列你不得不传递一个实参就是index_sequence这样子的实参。那么同样的,如果你有一个函数的一系列的型参你想要传递给另一个函数那么也是使用tuple,这个本是司空见惯的,可是假如你认为index_sequence和tuple能够达到完全一样的效果那就错了,因为这里牵扯到一个tuple里的型参是否都可以trivially_constructable的问题,比如你的型参里有类似引用类型,那么它们是不能随便生成的,那么你就没有办法这样子传递参数了。比如你有一个万能模板函数准备接受任何的参数类型 那么你就可以通过传递tuple来让它成为你任意想要的函数类型: 这个看起来很美好,可是如果你把其中的参数换成引用这个就不行了
    
    foo(tuple<int, string&, int>{}); // cannot compile because "string&" is not trivially_constructable!
    
    这样子是不行的因为你使用的是tuple的default的braced list initializer,这个要求tuple的成员都是能够trivially-constructable的。所以,这个是限制。
  2. 我又花了好大的力气实现了一个似乎没有什么大用的东西,就是对于任意一个functor实现它能够和lambda一样重载operator +以便返回一个函数指针使得这个函数指针能够作为它的operator ()一样来使用。这样做的好处是这样子的functor可以在参数为函数指针类型的地方自由的传递为参数,这个是lambda的一个相比functor最大的优势!
    1. 首先还是一个基本的获得functor的operator ()型参的元函数: 这里我做了一个微小的改进就是两个特例化都继承一个返回类就省却了重复定义返回类型的痛苦,这个是元编程的基本手法。
    2. 作为范例我们定义了一个貌似普通的functor,它有一个成员函数operator (),注意这里的参数是string而不是string&原因在上面提到,实际上就是tuple无法创建这种不能trivially constructable的问题,所以这个是对于参数的限制。
    3. 那么我们现在要接触核心了,既然是要重载它的operator+以便返回一个函数指针类型就是string(*)(string, int)注意它是和functor的operator()一模一样的。怎么做到呢? 这里我们需要用到上面得到的functor的型参的tuple来传递给这个内部的lambda result,而它仅仅是把型参传递给一个叫做Lambda的模板变量,那么核心就是这个模板变量是什么? 这个模板变量其实和之前我们硬编码的实现是差不多的,仅仅是使用了parameter pack免除了hardcode的而已,这里为什么要声明为static的呢?因为我们需要使用auto来推导它所代表的lambda的类型这个是很痛苦的,虽然我之前是可以通过retrieve计算出函数指针类型那么配合operator+是否可以不用定义为静态变量呢?答案也是不行因为retrieve依赖使用this导致出现错误invalid use of this at top level。这里我想使用this而不是类的实际名字Functor当然是期待能够通过类的继承把这个机制传承给子类,不过这个看来是做不到的,因为virtual不允许搭配auto。所以,继承不用想了。另一个小地方是使用decay_t因为如果类的对象是const,那么类的名字前面加上const是无法取地址的。
    4. 所以,这个就是结果: 可能你还看不出它的神奇之处吧?
    这个是代码
  3. 也许接下去我可以把重点转移到库
  4. 原来GDB里有一些特被为c++扩展的功能,很多我都不知道。
  5. 我记得我以前已经做过这个现在又忘了,就是对于uncaught exception可以使用这个来debug:
    
    std::set_terminate(__gnu_cxx::__verbose_terminate_handler);
    
  6. 看到这个关于如何判断一个lambda是否为stateless也就是说no-capture的情况,它是直接利用lambda的所谓的conversion operator成立的前提,我以为我可以使用:is_trivially_default_constructible来判断。
  7. 这是另一个c++标准文档的网站,它们都是非常棒的。

二月七日 等待变化等待机会

  1. range其实这么说太不公平了,range library是一个很大的库,单单这些分类access,primitives,iterator handling,concepts,views,factories,adaptors,等等,以前我对于这些统统混为一谈,以至于不知道如何入手,因为adaptor是最高层之一,而concept是限制指导使用,这些基本概念以前不清楚所以,才这么混乱。是一个看似简单实际很玄妙的东西,我始终入不了门,打算看看这个视频开开窍,结果一开始就看出一大堆的问题,需要补课。这个使用exchange来实现fibonacci数列做法就让人大开眼界。紧接着就是老问题:你会制作iterator吗?如果不会就不要说学习过c++了,我要从头学起,因为c++的精华就在于iterator,如果掌握不了,基本上属于练拳不练功的白辛苦!
  2. 如同一篇好的论文旁征博引一样,一个好的视频实际上是引入很多很好的线索来指导你的学习方向。准备花一个星期时间来重新学习iterator!

二月八日 等待变化等待机会

  1. 今天是重新学习iterator的第一天。
    1. 所谓的iterator需要具备这五个特征:
      属性解释
      iterator_categoryOne of the iterator_tags tag types
      value_typeThe type "pointed to" by the iterator
      difference_typeDistance between iterators is represented as this type
      pointerThis type represents a pointer-to-value_type
      referenceThis type represents a reference-to-value_type
      也就是说万变不离其宗,不管什么样子的iterator必须要具备这五样属性才能算是iterator。
    2. 那么理解了这一点就理解了为什么需要iterator_traits,它就是帮助你利用已经定义好的一些iterator的这五个属性的,因为它就是把这些属性重新定义了一遍。我觉得这个是继承思想的一个进一步,通常我们在面向对象里常常谈论的是继承的好处很少有人告诉你继承的害处,这里至少可以说出这些:
      1. 一旦继承就导致了类型的兼容与不兼容的问题,比如骡子是从马或者驴子继承来的,就有了骡子对象可以自由转换为被继承的马或者驴子的现象,很多时候这个是好事,但是很多时候这个是我们不希望发生的,尤其是iterator这个东西对于类型是很敏感的,我现在想不出来它的坏处,但是我认为这个至少是副作用,因为有的时候继承的目的也许很单纯就是看重了被继承类的某一个特征而已,比如我们看重的是马和驴子的善于驮负或者四条腿的特征,结果这样子的继承就太离谱了,所以,traits这种技巧就是把可以继承的特征剥离出来让其他的类可以很轻量的继承特征而不是继承整个的类!
      2. 第二个坏处其实和第一是紧密想联系的,继承导致了代码的臃肿,因为继承不仅仅是某些特征或者方法,结果连同实现方法的某些成员数据也继承来了,不要以为使用private/protected/public机制可以隔绝数据的使用,不要忘了,数据被继承了就是资源内存等等的浪费,这就是现在流行组合而不是继承的趋势,你继承的就是看重的方法,方法是不占用类的存储空间的,可是继承是连带数据都包含的,这个就是缺点。
      3. 这个也是和第二点相关的,继承带来的不仅仅是数据的臃肿还有副作用,因为你也许仅仅需要马和驴子的奔跑的属性结果继承带来的四条腿反而限制了奔跑属性的单一性,难道残疾的三条腿八条腿就不能奔跑了吗?人类的两条腿怎么办?还有其他无关的方法也被连带继承了,如果数据带来的是存储内存的浪费,那么具有副作用的这些无关的方法带来的错误就更加的致命,不要告诉我使用virtual可以避免,如果所有的方法都做成virtual的话就失去了方法的廉价,因为原本方法是不占用结构存储的函数指针,virtual是类的虚方法表的数据是存储的一部分。所以,这个不仅仅是资源朗费更是带来错误的。
      4. 继承导致代码重用性的降低,很多时候c++的交叉继承是被诟病的因为它的不确定性和复杂性,这个导致了很多时候编程设计的一条筋思维,如果你设计奔跑系列那么你的每个类都尽量的小而轻方便继承不要带来副作用,这个缺点当然也是显而易见的,当然是不容易需要很多的所谓的adaptor这类的高级技巧增加设计难度,代码碎片化其实未必方便重用,逻辑碎片化更加容易导致错误,很多复杂的过程需要方方面面的调度如果不在一个层面上实现其实导致出错的可能性也是很大的。总之是两难的问题。更加要命的是导致代码堆砌到一定阶段就无法再继承了,因为你发现如果需要某个特征而继承就意味着要剥离这个特征,但是设计之初也许它就是这个类的一部分,实现的过程是千丝万缕的牵扯到类的方方面面很难剥离,而且一旦剥离也许带来新的副作用,那么导致重用成为空话。当然反对者也可以举例说如果严格遵守面向对象的原则,这个特征应该被独立设计为一个类,可是现实是人类在编程很多时候为了一个特征设计一个五脏俱全的类也许不值得。话说回来了,难道traits本身不算类吗?这里就是另一个误区也是元编程的核心思想,类和型很多时候是一致的,但是类往往是有存储的是运行期有消耗的实体,而型是虚无的只有编译期存在的无存储的虚无,这个就是本质,traits说到底是型而不是类,虽然大家都有struct/class的外衣,但是它内部只是定义了一系列的类型而已,它是穿了类的外衣的型,没有认识到这一点就是以偏概全了。所以中文在这里是容易误导的因为我们总是泛泛的说类型似乎这是一个东西,实际上英文里这是两个截然不同的概念:class vs. type
      一个traits引发如此多的联想。我都忘了我当初看到了些什么。我其实想说的是大道理说了一千万最后还是要代码来说话,怎么做到轻量的继承呢? 这个就是很标准的概念,你想继承自一个前辈_Iterator的属性,你不需要去继承它,你只需要把它这些属性剥离出来通过这个廉价的型的剥离就足够了,注意这些是运行期没有存储和副作用的型而已!这就达到了不用继承的继承!
    3. 当然这里面的技巧还有很多,比如在concept发明之前如何判断这个被继承来的_iterator是否是合法的iterator呢?就是它是否真的有无大属性定义了呢?这里看到使用void_t的SFINAI的代码我就不细看了。另一个是对于普通的指针类型的iterator的实现,就是说iterator_traits<_Tp*>,这里又分为contiguous_iterator_tagrandom_access_iterator_tag两种,这里的意思是如果_Tp是类的对象就是前者,否则就是后者。而且前者是继承自后者,难道设计者当初是打算遍历结构体的每个成员变量吗?这个是前几天费尽心机搜寻的,而且我在GCC里找不到标准里说的那个实验性的experimental::reflect单元?
    4. 很有可能tag是iterator的精华所在,所以,有必要重点学习,究竟有多少种tag呢?我估计cppreference里可能能够找到这个表,不过自己总结印象深刻。
      tag nameinheritanceremarks
      input_iterator_tagMarking input iterators
      output_iterator_tagMarking output iterators
      forward_iterator_tagstruct forward_iterator_tag : public input_iterator_tag { }; Forward iterators support a superset of input iterator operations
      bidirectional_iterator_tagstruct bidirectional_iterator_tag : public forward_iterator_tag { }; Bidirectional iterators support a superset of forward iterator operations
      random_access_iterator_tagstruct random_access_iterator_tag : public bidirectional_iterator_tag { };Random-access iterators support a superset of bidirectional iterator operations.
      contiguous_iterator_tagstruct contiguous_iterator_tag : public random_access_iterator_tag { };Contiguous iterators point to objects stored contiguously in memory
      说到底tag的思想其实更加的接近于concept的一些所谓的概念,当然它可能最早还是从实现层面出发是所谓的tag dispatch的思路,总的来说也许concept是更加完善的思路是对于iterator传统的五大特征的扩展,也许这个五个特征并不足以描述,至少概念上不够清晰不利于拓展吧?所以真正的新时代使用的是concept。但是作为向后兼容以及实现的最终手段tag都是不可替代的。
    5. 这里我也终于发现我认知混乱的一个源头,在传统的concept出现之前我印象中的各种各样的实作的iterator和现代的各种各样的iterator的concept我搞混了。比如input_iterator现在压根不是一个实作用来继承的基础类而是一个concept概念!我怀疑我之所以有这个De ja vu的情节可能是历史上真的存在过用来继承的基础类叫做input_iterator,是后来概念出现才替代的吧?本来concept和模板就是相容的嘛。
    6. 不要以为从基础的iterator继承就拥有了什么,其实什么也没有因为说到底它就是一个类型而已,只不过给你要继承的类定了一个调子而已,就是帮助你强化了五大特征的定义而已。这个还是你指定的,所以,你自己制作iterator真的需要从iterator模板类继承来吗?并不一定,只要你自己在类里定义了五大特征就行了,这个是和传统的类继承有本质的区别,因为传统的物以类聚要求你必须从一个共同的祖先类继承而来,它最理想的就是一个和traits思想一样的型,但是很多时候设计者添加了私活定义了某些它们认为必须具备的基础的方法或者数据导致后来的变革者无法超越而不得不举行起义或者革命而反出山门另立门户,所以,traits的方式没有这种物以的限制,仅仅要求人以气投就可以,废除了宗派门阀思想显然是先进的,代表了未来的发展趋势。
    7. 所以说iterator本质上不过是iterator_traits用来比照的样板,这里连继承两个字我都不敢用,因为它真的没有这个必然性,只要有那个特征何必一定要有出生门第思想呢?欧洲的贵族血统论在东方可能是删除的最干净的,在两千年前就被废除了,朝为田舍郎,暮登天子堂在欧洲中世纪都是当作传奇写成历史,而在东方几乎就是一个普通现象。电影里的台词kneel as a man, rise as a knight被欧洲人民感动的热泪盈眶,传为佳话。在东方却没有那么多的感激之情。王侯将相宁有种乎?的发问早在两千年前就喊出来了,而今天在南亚次大陆种性制度的存在岂不是和奴隶制一样的落后生产力的代表吗?这样的国家也能崛起?
    8. 这里我又看到了新时代的iterator_traits的一个我之前没有注意到的东西,就是除了五大特征之外添加了一个iterator_concept ,不过不要被这个名字误解了它是什么concept,它是概念的实现吧?就是说它是新时代iterator的新特征,用类型类强调它的concept概念,也就是说达到物以类聚效果却不使用继承,或者说达到了类的兼容性而不使用继承,你仅仅是在这个category之外添加了一个综合的tag。这个非继承的实现可能也会导致传统的依赖于编译器内部函数__is_base_of的失效,所以,我觉得也许要判断specialization才对吧?
    9. 这里我认为不要被这个iterator_concept所迷惑,因为说到底它不过是一个代名词并不是真正的concept,似乎更加的像是恢复了一些类继承的血缘关系的意思,就是说凭着这个血脉证明来判断是否兼容,而不是依赖更加精准现代的concept凭借它是否具备某种能力来背书的一个传统的实现,因为很明显的例子就是如何定义一个或者支持input或者又支持output的iterator呢?从concept角度它很明确就是我对于背后的逻辑并不是很清楚,但是concept可以怎么定义都是灵活允许的,而tag就是冷冰冰的是或者不是的一个令牌,没有什么灵活性,就像封建贵族的头衔,有就是有,没有就是没有一样的死板 这个在类的继承上是无解的你不能说它或者继承自A或者继承自B,你可以有继承自A和B的类,但是从tag的角度来看几乎无法做到这个判断。比如 struct A{}; struct B{}; struct C:A,B{}; 你可以有三种不同的tag,你可能有第四种A或者B的tag吗?做不到啊,这里只有concept能够解决这类问题。
    好累啊!休息一下吗?
  2. 很多年前我嘲笑我的年老的同事认为他是English Programmer因为靠着他的母语能够理解代码字里行间的注释来编程,这当然是酸酸的自以为是。如今我也被自己当年的鄙视所困扰,很多时候就是凭借看武林秘籍字里行间的注释来理解代码,或者实在是理解不了的时候就上升到哲学高度把身段拔高,一旦务虚了,就无需真正的理解代码了,所以,务虚无需理解了,这个是务须要牢记的诀窍,因为毋须读懂是悟虚的结果,这个无绪才是无须争辩的。有朝一日,软件中心转移到了中国让中文注释成为主流,那个时候我还能看到的话一定要在注释里加入尽可能多的拗口深奥的中文,让母语非中文的程序员恨死!
  3. 根深蒂固的观念是函数不能仅仅是返回值不同而重载,但是模板函数可以,当然函数不能Partial specialization导致如果要重载返回值不同也导致了整个模板id是不同的。
  4. 我发现似乎这个是clang的bug,这里解释似乎是很令人信服的。这里的标准的例子是无可置疑的否定了在非模板函数里使用constraint的可能性。换言之,即便是模板函数的实例化也是不行的,因为实例化的模板函数和普通函数是没有区别的,而模板函数是不能特例化的。
  5. 这个纯粹是一个搞笑的东西,因为我对于函数重载不能是仅仅因为返回类型不同的根深蒂固的老观念想做一个挑战,这个当然是不可能的,不过如果使用functor至少可以看上去让同样的参数类型可以返回不同的类型,比如我们的functor总是接受一个string,但是它会根据不同的创建参数类型返回不同的类型: 当然这个实现是非常的简单,是平常人都能够想到的,毕竟模板函数和普通函数不是一个范畴的东西,谈不上重载,只能说是特例化。 这里值得注意的是我一开始正在犯愁怎么传入一个字符它的值是1,结果才想到要escape它\1
  6. requires之类的constraint是函数签名的一部分,所以,才能够在重载或者说是在模板解决过程中发生作用,但是这个和函数的类型是不同的概念,我以前可能把两个概念模糊了,认为函数的类型是解决的前提,其实在模板解决里依靠的是模板id,而模板id背后是一大堆的模板函数的签名队列,模板类应该也是如此,就是说函数的类型或者模板类的类型这些是早期c++的东西是可以兼容的吧?现代的requires之类的是改变签名而已。
  7. 一开始我被这个所谓的Named Requirements搞糊涂了,我以为这个都是标准指定的concept,实际上它们确实是抽象的概念,是concept的文本标准的定义吧?需要各个编译器自己去实现。这个是我的理解。

二月九日 等待变化等待机会

  1. 我时常在内心自问的一个问题是:究竟元编程有什么用,它是否真的是一个屠龙术?那么要回答这个问题也很简单,它在别的领域的用途我不知道,但是stl库就是大量使用这些元编程的技巧与工具,如果没有元编程的基础基本上看不懂库的代码,就凭这一点就足以说明它是否是屠龙术了。有时候一个基本的问题有千千万万的答案,最朴实简单的答案是自己能够看得到的答案。
  2. convertible_to为什么这么复杂呢?我以前一直不太理解为什么看似简单的一个类型兼容的问题要用一个很突兀的模拟函数返回值的SFINAE这样的方式来判断,难道元编程那么多的工具都不能直接依靠类型推导计算来解决类型的兼容问题吗?现在我才省悟这个问题实际上是一个没有实际常识的人才会发问的,因为类型的可否转换的根本原因就在于或者说根本的用途就在于函数的返回或者说类似于函数的constructor/conversion,你在什么情况下才有类型转换的问题呢?在c++这样子的强类型语言里任何变量的使用都要在声明之后,那么明确声明的变量何来可转换一说?那么只有从其他函数返回才有的问题,不要争辩说赋值语句,严格说来operator =也是一个函数,所以,它的使用场景决定了这个可转变的可否成立只有编译器才能做出准确判断,这个也是元编程的思想,如果编译器在编译期能够解决的不妨就问问编译器答案如何补就行了吗?前提是你能够使用一个尽可能高效的方式让编译器给你答案,很多时候我感觉requires这样子的概念就是编译一段代码看看程序能不能通的过,我内心有一种隐忧就是如果这一点被滥用,似乎你可以在requires里写一个复杂的函数是不是就达到了某种图灵机的难题:让一个程序模拟另一个程序的运行。
  3. iterator的实现有一个很重要的特点就是模板式的继承,它往往不是直接的继承,而是继承了它的某些属性。比如reverse_iterator从来就不是可以生生硬造出来的,而是从一个已知的的iterator改造部分继承来的,就是说它把要继承使用的iterator作为模板参数来使用,这个和面向对象的继承有着巨大的区别,因为你使用模板参数大部分时间是不能使用这个参数的对象成员,你使用的都是一些编译期可知的东西比如它定义的类型或者方法,而这些都是运行期没有什么存储成本的轻量级的虚的东西,就是说运行期都不一定存在的,当然方法是存在的但是类型却不一定存在。总之是继承了这个iterator的某些特征至于它实现的细节我们只是使用不一定要拿过来成为自己的一部分,这个和继承相比是有优势的,当然也是更难的做法。当然这种继承是有条件的比如reverse_iterator要求
    must be at least a LegacyBidirectionalIterator or model bidirectional_iterator
    前者是一个所谓的named requirements,后者是一个实实在在的实现的concept,这个要怎么理解呢?能不能理解成这样子:在concept还没有出现之前的c++20以前它就是一个可以文字描述无法用代码直接明确限制的概念,而在c++20的concept下是有规范可寻的实现了呢?
  4. iterator的概念是很重要的,这里的概念既可以是主观意义上的概念,也是客观代码里的c++20的concept。比如怎么定义这个concept呢? 我之所以引用这段代码还是因为cppreference的语焉不详,这里的derived_from的模板参数被红字表示为一个不明所以的/*ITER_CONCEPT*/<_Iter>这个是什么意思呢?只有看代码才明白这里的__detail::__iter_concept<_Iter>其实就是当前我们要继承来的iterator的自己的概念,这里的概念加引号的原因是在concept出现之前大家使用所谓的iterator_category(tag)来代表它是一个什么样的iterator,这个在早期的c++是够用了,那个时代没有人想到有类似range这样子把iterator运用的出神入化的地步,因为当时的iterator主要都是捆绑在container身上的小寄生虫,意思就是它们不大可能会脱离所依附的容器而单独存在,所以,就那么几种tag足够用了。但是当大家把iterator要泛化的时候,使用concept的优势就大多了,你可以组合更多的属性,这些属性就难以使用tag来一一对应了,不是不可以而是没必要,说不定我就是使用一次为什么要专门去实现呢?比如我只想喝一杯牛奶我需要花资源创建一个奶牛场吗?这个是编程的基本思想,所以,在传统的iterator_traits里是不暴露它有一个新的iterator_concept的成员类型,这个是不兼容legacyiterator的,所以,才需要在内部通过查询当前的这个iterator是否支持新的iterator_concept,如果没有就看能不能使用传统的iterator_category也就是tag。
  5. 感觉iterator大部分的代码其实都是在探究概念,因为其实实现的功能是很少的,说穿了就是怎么数数。或者说遍历的方法。
  6. 武林秘籍读了大半本,一挥手却不知道怎么出招,原因就是c++17以后就把iterator给deprecated,这个让我束手束脚其实这个并不代表不能用啊?可是我还是想看看有没有推荐的方法。最后还是忍不住看看别人是怎么做的,这篇文章似乎不错,至少它的这个图我喜欢。 而且我喜欢作者做的阐述方式,他实际上回答了我对于named requirements的疑问,我厚颜无耻的拷贝了他的表当然我也是有贡献的,我增加了对应的c++实现的concept的名称和连接。
    # Name Concept Description
    1 Input Iterator input_iterator Can scan the container forward only once, can't change the value it points to (read-only);
    2 Output Iterator output_iterator Can scan the container forward only once, can't read the value it points to (write-only);
    3 Forward Iterator forward_iterator Can scan the container forward multiple times, can read and write the value it points to;
    4 Bidirectional Iterator bidirectional_iterator Same as previous one but can scan the container back and forth;
    5 Random Access Iterator random_access_iterator Same as previous one but can access the container also non-sequentially (i.e. by jumping around);
    6 Contiguous Iterator contiguous_iterator Same as previous one, with the addition that logically adjacent elements are also physically adjacent in memory.
    所以,应该说这个才是大图景,所有的iterator都脱不了这个六大门派,至于说概念那是更细致的部分了。但是看了这个我才意识到同样的问题,就是你打算做一个有普遍意义的iterator供别人使用吗?因为你定义iterator_traits里那五大特征是为了给别人继承来用,否则的话,如果要做一个自娱自乐的最简单的fibonacci_iterator的话,你可以仅仅实现两个操作就是operator*,operator++ 当然这个前提是你必须这样子使用它,你不需要你的iterator定义begin/end,也只能使用后++,而不是前++,只使用operator*来获得结果, 是的,如果你只打算喝一杯牛奶的确没有必要去创建一个奶牛场!够用就是了,只不过这个是否真的有用呢?这个也是大师在视频里的发问,如果你写这些代码和你写循环没有什么区别何必要包装这个iterator呢?我以前对于很多面向对象的编程感到烦心的也是写了一大堆的boilerplate的代码实现的功能乏善可陈,似乎是为了遵循某些武林门派的找式而习武,并非是为了花最少的力气付出最少的代价战胜敌人的目的,这个如果在武林门派中存在自然也存在于编程学派。
  7. The Sum of All Fears是我看到的最好的一部电影,好就好在它是当前在20年后这个节点最好的回顾与教育。尤其是现在的普京大帝和俄罗斯人民。
  8. 这个是全世界人民都知道的常识了,就是在map之类的原来的comp这种functor可以使用lambda来替代,当然声明的时候期待的是类型,所以,你只能声明lambda的decltype,可是作为unique的lambda它的对象其实如果没有捕捉的话是没有区别的,所以,这就是关键不能有捕捉因为必须要default-constructible才行。

二月十日 等待变化等待机会

  1. 昨天脑子没有想清楚,就是说iterator有大致两种不同的使用场景,一种是依附于容器或者某种数据结构算法来方便遍历隐藏实现细节让用户代码应用起来一致或者通用,这个应用场景当然没有必要要求做到合约制,你只要满足用户的使用习惯简单实现前进后退等等使用方法。可是还有另一个大的使用方式就是利用库里面大量的现成的算法来实现的通用计算,这些算法就对于什么样的iterator有明确的要求的,这个才是最大量的应用而这里才是概念限制的重要需求。所以,检验我这个自己做的iterator是否真的满足input_iterator的要求就随便找一个算法来检验一下。比如使用最简单的all_of/any_of就可以。正像大师解释的那样子,作为input_iterator的基本要求还包括了实现operator==,这个我使用default就足够了。 any_of/all_of只需要input_iterator就足够了,所以,无法检验我实现的更高级的bidirectional_iterator的功能。但是至少知道这个对于input_iterator是足够了。

二月十二日 等待变化等待机会

  1. 俗务缠身,不能自拔。我想尽快的过一遍iterator因为没有具体实作的空谈是浪费时间,所以,我决定看看最最常见的stream_iterator。这里马上就遇到了char_traits这个问题,而它和基本的string又是紧密相联的,往往就是这样子看到一个就万事万物往往都是联系的。我对于这个char_traits如何重载还是看不明白,似乎更多的牵扯到和locale的相关部分。我一直以来对于char8_t的char_traits没有在库里感到不解。
  2. 看到了似曾相识的ostream_iterator,其实我都不记得我在什么情况下使用过它?它的代码是如此的简单,仅仅就是实现了一个operator =就是交给下面的ostream去做operator <<,怎么使用呢?我生生的想出了这个简单的做法,它的意义在哪里呢?
    
    ostream_iterator<const char*> it(cout, "\n");
    it = "hello world";
    it = "a new world";
    
    就是说我们不再依赖ostream的操作符而是直接分门别类的调用ostream各种重载的operator <<。注意到ostream_iterator的所有操作几乎都是空的返回*this,这个也就是它比istream_iterator简单的多的原因。而且相对于istream_iterator还需要一个default_sentinel_t来协助,这部分我还是没有很清晰,似乎这个是被很多iterator都使用的一个检查终止的类型,这个是否就意味着大家都要重载和它比较的操作符operator ==?这个是我直观的感觉,但是事实上不是的,因为大家比较还是和default-constructible的对象来比较,因为里面使用一个标志量_M_ok,所以,这一点似乎是历史问题,照理说大家都应该和这么一个成员来比较istream_iterator(default_sentinel_t),但是大家平常写代码没有人这么愿意使用这个形式
    
    std::istringstream str("0.1 0.2 0.3 0.4");
    std::partial_sum(std::istream_iterator<double>(str),
    	  std::istream_iterator<double>(default_sentinel),
    	  std::ostream_iterator<double>(std::cout, ","));
    
    大家都依赖于ostream_iterator的default constructor创建的是和正常的ostream_iterator不相当的特性,但是假如我们创建失败呢?这个真是多虑了,失败就失败无非就是没有进入循环而已,难道有人会写一个死循环吗?总之默认创建的是一个invalid的对象的思路反而是一个防止死循环的办法,似乎我原来代码里也是有这个意味? 直接操纵iterator而不是stream类有什么好处呢?大概就是防止类型不匹配吧? 这个时候回过头来看istream_iterator才意识到它只有在操作符operator++的时候才去调用内部的所谓的read方法,其实就是istream的操作符operator >>至此我似乎终于对于i/ostream_iterator有了一个比较清晰的认识了。这个是今天的收获。
  3. 在开始streambuf_iterator之前还是再回顾一下我以前犯的那个刻骨铭心的错误,就是这个读取代码到string的根本原因是什么? 实际上就是利用了istream的对于char类型的操作符operator <<,那么不可避免的是它是需要分隔符的,这个是默认的实现无法改变的,所以,才有streambuf_iterator的更细的操作的需求。粗略的感觉似乎是这样子的就是stream的行为是由streambuf_iterator来实现的,也就是说真正的工作都是它完成的,而相对应的stream_iterator不过就是躺在stream身上的简单提供遍历的接口而已。

二月十三日 等待变化等待机会

  1. 对于streambuf_iterator有这么一个总结:首先ostreambuf_iterator的方法要简单的多,这个和input_iterator比output_iterator复杂的多是类似的。其实它就主要一个方法就是operator =这个本质上就是putc。
  2. 对于istreambuf_iterator就复杂的多了,首先,operator=是istreambuf_iterator的赋值操作而不是ostreambuf_iterator针对char元素的操作,这个毫无可比性。而对于operator ==的逻辑我还是看不懂,仅仅是比较是否大家都是eof吗?两个iterator的相等的概念和他们指向的元素的相等是风马牛不相及的。只有的iterator和不好的iterator,似乎只要是活着的ostreambuf_iterator就是一样的?
  3. 此外有两三个friend的方法copy和find/advance很重要,前者是和两个都有关系就是常常看到的copy(istreambuf_iterator from, istreambuf_iterator to, ostreambuf_iterator dest),而后者仅仅是针对istreambuf_iterator的方法,find是在istreambuf_iterator的range范围内搜索,而advance是跳跃n步长。
  4. 但是iterator是和它所附属的对象紧密联系的,如果不结合起来看是不是太片面了?我从来没有接触过streambuf这个类,看起来是挺复杂的。
    The I/O stream objects std::basic_istream and std::basic_ostream, as well as all objects derived from them (std::ofstream, std::stringstream, etc), are implemented entirely in terms of std::basic_streambuf.
  5. 我常常对于操作符重载感到困惑,比如i/ofstream对于streambuf都有重载operator<<operator>>
    
    ifstream in1("test1.txt");
    ifstream in2("test2.txt");
    ofstream out1("test1.txt");
    ofstream out2("test2.txt");
    
    代码结果
    in1>>in2.rdbuf();两个文件统统被清空了!这个是最可怕的操作!
    in1>>out1.rdbuf();两个文件同样被清空了!这个也是最可怕的!因为即便操作没有涉及到test2.txt文件的in2/out2可是灾难似乎波及到了打开的文件流导致in2/out2虽然什么也没有做也遭殃了!
    in1>>in1.rdbuf();令人不惊讶的是两个文件都被清空了!你对于streambuf的不适当的操作结果都是可怕的!
    in1>>in2.rdbuf();令人不惊讶的是两个文件都被清空了!你对于streambuf的不适当的操作结果都是可怕的!
    out1<<in1.rdbuf();又一次的灾难两个文件都被清空了,看来每次发生灾难的时候,不相关的fstream也遭殃了。
    out1<<in2.rdbuf();又一次的灾难两个文件都被清空了,看来每次发生灾难的时候,不相关的fstream也遭殃了。
    out1<<out1.rdbuf();又一次的灾难两个文件都被清空了,看来每次发生灾难的时候,不相关的fstream也遭殃了。
    out1<<out2.rdbuf();又一次的灾难两个文件都被清空了,看来每次发生灾难的时候,不相关的fstream也遭殃了。
    总而言之,这种投机取巧的操作是灾难性的,就是说一个正在被读的文件同时打开写的流是危险的,不管是否有参与,这个似乎是因为他们所指向的streambuf内部都是一个对象的原因吧?我使用gdb跟踪非常的困难,因为代码指针跳来跳去看不懂流程。总之,要绝对避免。

    所以,正确的打开方式是

    代码结果
    
    ifstream in1("test1.txt");
    ofstream out2("test2.txt");
    in1>>out2.rdbuf();
    
    test1.txt的内容被拷贝到了test2.txt这个是相当于打开in1作为读,同时打开out2作为写。
    
    ifstream in1("test1.txt");
    ofstream out2("test2.txt");
    out2<<in1.rdbuf();
    
    test1.txt的内容被拷贝到了test2.txt这个是相当于打开in1作为读,同时打开out2作为写。
    
    ifstream in1("test1.txt");
    ofstream out1("test1.txt");
    in1>>out1.rdbuf();
    
    test1.txt的内容被拷清空了,这个是常识,因为没有人会怀疑自己读并自己写会有什么结果,只不过是否还怀疑会不会文件保持原样呢?
    
    ifstream in1("test1.txt");
    ofstream out1("test1.txt");
    out1<<in1.rdbuf();
    
    test1.txt的内容被拷清空了,这个是常识,因为没有人会怀疑自己读并自己写会有什么结果,只不过是否还怀疑会不会文件保持原样呢?
    总而言之,ifstream和ofstream对应的是不同的文件,基本上是没有风险的,不管你是把ifstream调用它的operator>>还是调用ofstream的operator<<结果都是相似的。

  6. 文件流其实是很复杂的我看到一个判断流是否是tied,这个概念我从来都没有,据说cin和cout是tied。这个是什么意思呢?是不是可以通过这个例子来解释之前我遇到的问题呢?比如:
    
    std::ofstream os("test.txt");
    std::ifstream is("test.txt");
    std::string value("0");
    
    假如我们使用tie的话:is.tie(&os);就相当于每次都调用了os.flush();,比如
    
    os << "Hello";
    os.flush();
    is >> value;
    
    这个方法只是istream的方法,不能是ostream的方法,我觉得这个真的是内部使用就好了,真的不应该公开。
  7. 这个basic_iostream存在的目的是什么呢?是为了继承性吗?把istream和ostream的方法共性放在一起来被继承吗?看来我理解错了,是一些独特的读写一体的类的需求。我喜欢这个图就扣下来保存了。 cpp/io/ios base cpp/io/basic ios cpp/io/basic istream cpp/io/basic ifstream cpp/io/basic istringstream cpp/io/basic ostream cpp/io/basic ofstream cpp/io/basic ostringstream cpp/io/basic fstream cpp/io/basic stringstream cpp/io/basic iostream std-io-complete-inheritance.svg

二月十四日 等待变化等待机会

  1. 我偶然发现了我以前的错误的一个小小的改正结果却偏差了十万八千里,着不回来了。
    
    filesystem::path currentPath{string(filesystem::current_path().c_str())+"/"+source_location::current().file_name()};
    ifstream in(currentPath.c_str());
    std::noskipws(in);
    string str{istream_iterator<char>(in), istream_iterator<char>{}};
    
    这里的关键就是当初使用istream_iterator的时候依赖于istream的读取char的重载方法也就是operator >>,而它是默认使用whitespace作为delimiter的,我以前一直没有意识到ios基本类是可以改变这个就是使用noskipws

    不过我在这里就偏了,因为获取当前代码文件路径的老问题,就在于filesystem::current_path()这个函数获得的是所谓的当前目录这个和可执行程序的运行期的当前目录不是一回事,因为我在编译期的代码编辑环境里的当前目录和目标程序的运行期的当前目录是可以不同的,尤其是eclipse这一类IDE是肯定做了分离的,结果导致我得到的仅仅是代码编辑期间的current_path,而且这个source_location获得的代码文件目录也是一个编译指令的反映,就是makefile传递给编译器比如gcc的代码文件路径,甚至很多编译选项直接设定了编译宏的FILE也就是说__FILE__不一定是当前代码文件的路径,可以是任意的,这个典型例子就是bison里面有一个选项可以指向语法文件而不是产生的代码文件,也就是说这个是不可靠的,eclipse里虽然没有指向任意文件,但是它使用了一个相对路径,这个也是让人头疼,以前debug的时候因为编译的程序部署到另外的机器,直接拷贝的代码路径和编译机器路径不一致所以debug信息里如果代码路径是绝对路径是很麻烦的,gdb无法定位代码路径,使用相对路径反而是好的。当然也是要在gdb里添加一个搜索路径,尽管麻烦也是可行的。但是现在这个看似简单的问题确实很啰嗦。

    1. 我获取程序运行路径的方法以前是使用boost的dll库,但是为了一个函数添加一个boost实在是麻烦,就看源代码就是直接read_syslink("/proc/self/exe");
    2. 代码路径,不管是使用c++20的source_location还是直接使用__FILE__得到的结果都是相对路径,所以,我想使用canonical来化简。
    3. 所以,这个就是获得当前代码的路径的比较可靠的方法:
      
      filesystem::path exePath=filesystem::read_symlink("/proc/self/exe").parent_path();
      string str=filesystem::canonical(exePath/=experimental::source_location::current().file_name()).c_str();
      
      对于喜欢故弄玄虚的oneliner就是这样的到path:这里是比较早期的gcc-10.2.0的设置,就是source_location还在experimental目录下面没有转正
      filesystem::canonical(filesystem::read_symlink("/proc/self/exe").parent_path()/=experimental::source_location::current().file_name())
    4. 我才意识到一个基本的基本的常识,我怎么这么愚不可及,如果不和range结合,制作iterator几乎是毫无意义的,因为,首先,各个容器自带的iterator不用你去费力的重新制作。其次,单独的iterator几乎没有代码重用的可能性,因为只有range才能做到重用。比如,我异想天开做一个line_iterator想把getline这个函数包装起来,可是最低限度我如果仅仅是使用一个循环来做的话,我做这个包装完全没有任何的增益! 这是一种令人作呕的c++编程,就像当年很多c语言编程家模仿面向对象编程把无数的函数包装成一个一个类,这个在java甚至走向了极端以至于程序入口的main函数都要包装为一个类带有一个静态方法,这个究竟有何增益?除了形式上的整齐画一增加了无用代码以外,对于仅仅使用一次的代码有何必要进行包装,如果代码不是有复用的可能性根本不需要任何的改进。 我仅仅是使用了所谓的iterator的一个operator++,operator*和operator==,这个有什么意义呢?如果不和range结合完全没有复用的意义,因为我才意识到实际上这个range-based for loop需要你实现一些基本的方法,比如begin/end而这个是我感到困难的。

  2. 找到官方的说法这个帖子水很深可是我应该以前已经点赞过了,可能没有看的很细吧?,我匆匆看了一眼,相信有很多高深的地方,只不过我现在没有时间去细看。
    The closure type for a non-generic lambda-expression with no lambda-capture whose constraints (if any) are satisfied has a conversion function to pointer to function with C++ language linkage having the same parameter and return types as the closure type's function call operator. The conversion is to “pointer to noexcept function” if the function call operator has a non-throwing exception specification. The value returned by this conversion function is the address of a function F that, when invoked, has the same effect as invoking the closure type's function call operator on a default-constructed instance of the closure type. F is a constexpr function if the function call operator is a constexpr function and is an immediate function if the function call operator is an immediate function.
    这里non-generic是否包含了template lambda呢? 这一段代码clang不接受,难道是因为non-generic吗?但是gcc和msvc都错了吗?且慢,标准关于non-generic或者说template lambda也是有规定的。
    For a generic lambda with no lambda-capture, the closure type has a conversion function template to pointer to function. The conversion function template has the same invented template parameter list, and the pointer to function has the same parameter types, as the function call operator template. The return type of the pointer to function shall behave as if it were a decltype-specifier denoting the return type of the corresponding function call operator template specialization.

    而这里是关于什么是generic-lambda的定义:

    A lambda is a generic lambda if the lambda-expression has any generic parameter type placeholders ([dcl.spec.auto]), or if the lambda has a template-parameter-list.

    决定提交这个bug给clang。
  3. 报告这个ICE给GCC。我觉得这些都是lambda在unevaluated-context情况下的相关问题,尤其是lambda内部的那个conversion operator的相关问题。
  4. 我之前提交的bug不被接受,那么我就再提交新的,类似的相关问题。
  5. GCC有个大神一个星期报告了26个bug而且很多都是他自己fix或者confirm等等的,大部分都是毫无疑问的ICE。看email是一个来自捷克的搜索引擎公司Seznam.cz。捷克虽小出人才啊!

二月十五日 等待变化等待机会

  1. 究竟在没有返回值的函数体是否可以促使returr-type-deduction工作推理出autovoid呢?我不确定这个算不算是一个bug呢?但是crash肯定是,所以,我就提交了clang的bug。
  2. 我以为这个问题问的相当的犀利直接戳到了我的心窝里: What is the difference between a template-name, template-id and a type-id? 这一段论述我在最新版的标准里找不到了:
    In a function template declaration, the declarator-id shall be a template-name (i.e., not a template-id). [Note: in a class template declaration, if the declarator-id is a template-id, the declaration declares a class template partial specialization.
    我看的不太清楚,这个比律师的工作还复杂还要咬文嚼字。但是概念是不可以有差错的。不过我觉得我的概念是正确的,这个问题我基本上也能回答出的:
    1. The template-name is the name of the template.
    2. The template-id is the name of the template with the template arguments list.
    3. A type-id names a type. A template-id is a type-id; a template-name is not (because it does not name a type; it names a template).
    我开始有点想通了,因为c++允许函数名字被转为一个变量名,或者说地址?也就是说函数指针。这个是大家司空见惯的。 这里的bar<int>是什么?如果bar作为template-name是一个类的话bar<int>代表一个类型,可是bar是一个函数,那么在打开所有的警告-Wall终于看到
    
    warning: statement is a reference, not call, to function ‘bar<int>’ [-Waddress]
        5 |         bar<int>;
          |         ^~~~~~~~
    
    GCC认为它是一个reference的statement,而msvc则坚持认为这个是函数调用没有参数发出警告。这个答案还是可以的。
  3. 这个可以作为小品文的素材了。如果不使用alias的话 怎么解决呢?使用alias 或者更加简单的使用named lambda。 但是这么做的缺点是你使用了一个局部定义的lambda,你是无法把这个set/vector放到其他地方使用的,不过这个也许正是lambda的真谛,就是它就是一个局部inline的小函数,不能指望你把多么复杂的操作写成lambda而放在广泛的地方使用,这样子的话你肯定不应该使用lambda。所以,这个也是佐证这么做的理由。
  4. 我模仿大师的做法,因为我发现直接使用view_interface来继承创建一个view的话,其实还是有不少工作要做的,不如直接使用subrange的实例方式: 就是说我如果仅仅实现了一个input/output_iterator的话就可以使用view的方法了,这个真的是大大方便了!否则让我自己考虑如何在end()返回sentinel我还真的不明白要怎么做。这样做的好处是至少你可以假装让用户使用range-based-for-loop,比如: 当然这个是死循环,我还是需要再想想看有什么其他的选择。但是它的有点是非常的诱人的,因为这里的it直接就是我需要的fibonacci的结果int,根本不暴露其中让人繁琐的fib_iterator这种无意义的类型,用户仅仅想要结果根本不想接触这个那个的iterator,因为我自己都搞不懂我的iterator是什么类型的,何必要让用户也烦恼呢?
  5. 我前两天看到了ws结果来不及记录事后居然忘记了。先记录下来。因为这个例子非常的让我震撼,cppreference里好就好在很多的例子是非常的充满智慧的前辈高人精心打造的。首先,我要明白这个是c++98之后的c++11的新语法才有的不同于古代的for-statement,应该叫做iterator-statement,它包含了古代的for-statement,而新的是for-range-declaration。换言之,range如果仅仅是一个library是无法做到的,是编译器的语法有了专门的支持才做到的。这里我还是误解了因为我没有意识到这个是语言本身的支持
    
    for (int n : {0, 1, 2, 3, 4, 5}){} 
    for (const char* str: {"     #1 test", "\t #2 test", "#3 test"}){}
    
    这里的initializer_list是不需要额外的range的支持的这里根本不是什么initializer_list因为它是一个模板类,这里就是一个数组或者说是brace-init-list,而因为std::begin/end实现了对于它的支持,所以,你才可以把它们拿来作为range使用,也就是说必须支持range的基本接口,我感觉这个也许是view_interface吧?。这个是所谓的brace-init-list,而这里的两个是initialization of an unnamed temporary with a braced-init-list

    我也终于看到了在range_access.h里如何实现begin/end对于数组的重载,这个本来是所谓的常识,但是我却没有把这几件事清联系起来:

    1. 首先是c++11从语法上支持了range-based-for-loop
    2. range-access实现了很多的支持比如对于数组的begin/end。而很多的容器或者类似容器比如initializer_list自带begin,那么range也要适应一下来用begin包装这些类的begin的成员函数从而达到符合range/view要求的。
    所以,这两个条件缺一不可。这就是我关于这么一个最最基础的如何任意一个类能够支持新型的for-loop的理解。

  6. 我记得一两年前就遇到这个问题,想要改变ctype<char>是徒劳的,因为它本身就是specialization而且不再开放virtual的方法,你把继承来的指针传递进去stream即便imbue了你创建并替换的facet也是无用的,因为还是你继承来的类型的方法被调用,除非你去修改ctype_base的常量,这个也太危险了。这里我唯一学到的新东西是我必须要重载我继承来的destructor,因为它必须要是virtual的否则就是crash。 这里我重载is方法是徒劳的,因为它不是虚方法,只有ctype<wchar_t>有开放do_is这样子的虚方法允许你继承重载。所以,白辛苦。
  7. iterator是c++标准库的精华之所在,是基础的基础,是贯穿于整个标准库的方方面面,如果能够轻易掌握几乎就是说可以轻易掌握c++标准库了吗?看来一个星期是吹牛了,大大的吹牛了。至今我还是对于各种各样的tag和concept以及各种各样的只读只写的关系还是一团浆糊。

二月十六日 等待变化等待机会

  1. 对于这个same_as存在的必要性我感到有些惶惑,难道模板的参数的比较也会发生类型转换吗?否则的话为什么需要is_same<U,T>is_same<T,U>同时成立呢?我是想不出有什么样子的类型需要这样子的判断。
  2. subrange是一个utility就是说它能够依赖你提供的iterator参数来推理出它背后所代表的事物是否是一个range或者是什么样的性质的range。这个比从view_interface继承来说是更容易的,前提是你已经有了合法的iterator,究竟哪一个更普遍呢?view_interface很可能你还是要去实现iterator,所以,subrange是你已经有了基础搭一个便车。
  3. 我发现我的知识体系有一个很大的漏洞那就是内存部分,通常这个是程序员最关心也最容易入手的部分,而我却长久的缺失。所以,现在转变进攻方向,准备花最多一个星期攻克这个长久以来的痛点。
    1. 第一部分当然是所谓的指针部分,有四大名捕:
      • unique_ptr
      • shared_ptr
      • weak_ptr
      • auto_ptr
      排名不分先后吗?最后一个是在c++17里面被剔除了。
    2. 指针里第一个碰到的就是default_delete这个似乎是很简单的东西,难道不就是把所谓的delete包装一下的functor吗?它要做什么判断呢? 这个是它针对非数组类型的函数,为什么需要判断sizeof(_Tp)>0呢?难道有什么类型是小于或者等于0的呢?小于是不可能,但是等于0倒是有,我第一个想法是空结构,但是这个首先就被否定了,因为至少GCC编译器是分配1给空结构。有什么是大小为0的对象呢?我能想到的是长度为0的数组比如 那么你要怎么new或者malloc这个数组呢?首先崩入脑海的往往是错的new int[0];返回的是int*,也就是说指向int[0]的指针类型int(*)[0]是一个怎样的指针呢?是int**吗?可是编译器不同意。 网上的答案很多时候其实也是误导的,如果没有判断力,反而是越学越坏。我没有看到一个像样的答案,所以,我只能使用我的malloc的workaround来做。 可是这样子的结果就是第一你不能使用default_delete了, 因为以上的那个要求sizeof(int[0])>0是不成立的。 所以,我只能使用free,因为我是使用malloc分配的,这个是第二个原因。怎么办呢?
    3. 感觉c++实现者认为int(*)[0]这样子的指针是无意义的?没有人要这么分配指针,指向一个空无一物的对象。所以,这里你才能意识到你有时候只能自己使用自己构建的deleter。比如 这个是没有办法的办法。
    4. 提交了一个GCC的bug,因为这个是很奇怪的GCC的c++20才有的问题,基本上大家都认为一个类型如果它的size是0,就不值得为它分配内存,可是这个论断是否有些武断呢?如果是这样子的话malloc(0)就不应该返回指针啊?事实上malloc(0)是照样返回一个有效的指针,而且你不释放Asan还会发现你内存泄漏了。这说明什么呢?内存大小为0也是有内存的分配问题的,我猜想是实现机制要求每次分配必然是指针本身也要藏在分配的内存块的前部所以你要求的即使是0也有一个损耗。所以,这个限制导致你无法使用普通的make_unique来做,而且普通的unique_ptr也不难直接分配。 所以,类似的这个也是这个奇技淫巧的方式: 不过这个有个小问题就是直接使用这个匿名lambda导致你无法使用move也就是说 还是老问题,就是使用一个named lambda: 这样做有一个意外的好处是我们这个unque_ptr只能在本地使用,因为它的类型是依赖于本地的lambda实例,强迫本地劳动。
  4. 一个简单的练习题就是确定括号是否匹配。

二月十七日 等待变化等待机会

  1. ubuntu的新的启动程序管理器改名了吗?gnome-session-properties
  2. 这个是一个基本的实践的常识,就是实现类包装在unique_ptr里,不过这里提出的关于const方法的问题对于我还是太超前了以后再说吧?
  3. 文档,看代码, 其中的__invalid_make_unique_t是一个元函数当_Tp是数组的时候返回 我才意识到这个make_unique<int[10]>是被禁止的!原因是什么我一开始想不清楚,看了这个很棒的帖子,大概理解了,这个是无解的难题索性就禁止了。这里是原始的标准建议书。问题是为什么make_shared不做相似的动作呢?与此相反的是这个建议make_shared增加对于数组的支持!看了这份建议我才理解,原来这一切都是为了能够初始化数组而做的努力!至于说为了满足类型信息倒是次要的而且也是不可能的!比如原始的make_shared<int[4]>实际上是转化为了make_shared<int[]>(4)这里的4是作为数组的长度来传递参数的,而很多程序员希望把这个机会留给数组初始化的参数,这个才是提出这个建议支持数组的初衷哦!

    但是这里有一个烦人的地方就是不管你是

  4. 我真的是无语了,整个早晨就被迷惑为什么对于数组类型的unique_ptr的operator*找不到,害得我还去gdb/gcc,简直是笨死了,难道文档没有看懂吗?operator*operator[]是分别重载了的啊!也是我过于依赖eclipse里的代码概括工具,居然看不到两个不同的模板参数!真的是闹大笑话了。其实我早该想到是这样子的简单的模板参数的特例化 这个是一般模式实现了operator* 而这个是针对数组参数实现的特例化 我其实一直在找类似的这个可是在代码里就是没有看到!是眼睛不行还是脑子不行?
  5. 这个就是我这两天的核心问题differences between make_unique and make_shared when handling arrays
    1. unique_ptr不支持bounded_array,就是说模板参数不能是T[n],只能使用unbounded_array,就是T[] 所以,对于数组int[10]来说只能这样子< 依靠参数10来说明数组的长度。
    2. 而shared_ptr却完全支持所有的数组类型,甚至高阶数组,而参数被用来作为数组元素初始化了。
    3. 但是不要被shared_ptr的貌似支持数组类型所欺骗,它内部是根本不储存数组的长度的,也就是说不论unique_ptr还是shared_ptr最终它都是把数组当作是指针来对待的,你是得不到模板参数的原型的 注意到这里shared_ptr的element_type是remove_extent_t就是去掉了数组后的元素的类型。
    4. 基本上两个都不能处理带参数的数组的形式,除非元素是可以有默认初始化的,否则数组类型是不可能创建的。
  6. 这里是一个基本的常识,我似乎总是模糊就是对于一个类型的constructor,哪怕是一个POD它也是代表了constructor,不管是(),还是{}
    
    char buf[sizeof(int)+1]={"1234"};
    auto p=new(buf) int{};// same as () both initialize to 0
    assert(*p==0);
    
    注意这里我们使用自己的栈来做new的内存分配,不要再调用delete释放了 就是说如果有了constructor那么int()或者int{}都初始化了,而c语言的习惯不加括号则是不初始化。
    1. unique_ptr可以传递给shared_ptr,但是必须要unique_ptr释放所有权,比如通过move 而有趣的是无名的变量也就是临时变量本身就是rvalue,所以,这个也是合法的: 结论就是uniqiue_ptr是独占的是不可能从shared_ptr夺回所有权的。因为shared_ptr是一个非常负责的家伙,绝对要保证它所管理的内存一定销毁,除非交给另外一个shared_ptr,同样也是负责的家伙。而unique_ptr则可以主动放弃 所以,结论就是unique_ptr可以转给shared_ptr,但是,一入豪门深似海,再也回不来。而unique_ptr转给shared_ptr的方法是要么release要么move。
    2. 所以,类似的unique_ptr之间也可以做类似的交接,就是使用release或者move,前者是裸指针,后者是对象通过operator =
    3. weak_ptr只能接受shared_ptr或者其他的weak_ptr。weak_ptr是不能从unique_ptr得到指针的。所以或者是constructor或者是operator=都可以让shared_ptr赋值给weak_ptr。这也适用于weak_ptr之间的赋值。 而这个更有意思就是如果是匿名的shared_ptr那么会立刻消失的。
    4. weak_ptr不可以独立创建,也就是说从其他那里接过来,不能接受裸指针。weak_ptr之间还可以swap。最后最有特色的是weak_ptr可以通过lock方法来传递给shared_ptr
    5. 作为shared_ptr获得指针可以通过constructor或者reset获得裸指针。通过operator=,或者swap获得其他的shared_ptr的对象所拥有的指针所有权。 那么通过其他的shared_ptr分享所有权是operator =或者是swap,或者就是constructor。这里有趣的是swap或者operator=都不怕自己赋值或者swap自己,这个是安全的 注意这里都是分享,而不是所有权的转让,所以,owner_before都是假的。 这里的owner_before的逻辑其实有很大的迷惑性,到底我要比较的shared_ptr是空指针吗?
    6. 关于swap我其实还是判断不清楚,比如这个我就做错了 从逻辑上说应该两个shared_ptr都是所有者吧?,那么这个故弄玄虚的swap和operator=有什么区别呢?

二月十八日 等待变化等待机会

  1. 有一件事情是很重要的,就是shared_ptr的constructor如果承接的是一个空的weak_ptr是要抛出异常的,而如果是一个空的shared_ptr则没有问题,而且针对于shared_ptr有重载两种形式是共享还是move,这两个形式对于空的shared_ptr的作为参数都是无害的。唯独对于weak_ptr是做了严格的检查,这个应该也是强调当把weak_ptr传递给shared_ptr的时候最安全的是使用lock方法,因为lock在weak_ptr为空的时候会创建一个空的shared_ptr从而避免了抛出异常的可能性。
  2. 所以,enable_shared_from_this能够做到是因为它是shared_ptr的内线早已在shared_ptr创建的时候就有内部操作这个骚操作只有继承自它的类使用它特定的成员方法shared_from_this的时候才能达成效果,它是直接返回内部储藏的一个weak_ptr,当然这个weak_ptr只有当类是继承自enable_shared_from_this的时候才能被初始化。而这个enable_shared_from_this其实是相当复杂的一个使用场景,这里给出的例子非常详尽的阐明了这个骚操作的陷阱:
    1. 作为要把自身作为shared_ptr输出给别人,很可能是作为方法里的参数让使用者可以放心大胆的使用自己的this指针,这个本来就是匪夷所思的使用场景,那么它的应用一定是有前提的,对于这个前提不清楚那么一定是要犯错误的。
    2. 首先这个被传递出去的shared_ptr绝对不可能从天而降的,一定是有人已经创建了,可是要怎么创建呢?我翻看以前的笔记还是没有真正的深刻理解,但是当时肯定是理解了很多只不过我现在又忘记了不少。
    3. 核心的问题就是我们要调用的这个shared_from_this一定是一个shared_ptr里指针才行,那么这里就有一点点鸡生蛋蛋生鸡的困惑了,我们怎么能够自己就把自己放在shared_ptr里呢?如果有了我何必还要这个方法呢?因为我们的目的就是要把自己放在shared_ptr里,这个就是循环的悖论。
    4. 所以,这个实际上是一种使用场景的限制,我们等于是说要在使用之初就预见到了我们需要在使用过程中有需要把自己给share出去,那么就尽早把自己先放在shared_ptr里吧,而要强迫用户做到这一点就是使用factory模式强迫用户这么做,这里要防止的就是不明就里的用户随手把自己的this指针放在shared_ptr里传递出去,这个当然是初级的错误,但是也不能排除高级的错误,因为任何用户看到你的类继承自enable_shared_from_this继承了方法shared_from_this可以返回用户需要的shared_ptr都有可能经不住诱惑直接调用返回,这里的陷阱是什么呢?因为用户有可能是从一个并非包装在shared_ptr里的指针调用的,如果是从一个普通对象里调用的shared_from_this的话是会抛出异常的,就是说这个方法必须是从一个shared_ptr里调用才行,或者至少this指针已经被某个shared_ptr所拥有,这个就是最常见的一个陷阱。因为这个概念很难保证一定被程序员所遵守而导致异常发生很可能是难免的,所以,才会有极端的干脆禁止用户使用default constructor能够创建的auto variable,只开放一个factory方法让你创建shared_ptr包裹的指针。这个是要具有预见性的设计意识到用户有这种把自己的this指针分享出去的成员方法才行。这个需求其实是非常特殊的。
    5. 我能够比两年前看到的更多一点的是为什么shared_ptr里需要一个很突兀的方法__enable_shared_from_this_base,其实它就是要看看当前的类是否真的是从enable_shared_from_this继承来的模板类,严格的说是借助了函数参数兼容的测试达到的,这个应该是比较没有c++版本限制的通用的做法,而我脑海里想到的通过is_base_of至少还是c++11,不过这个shared_ptr不也是那时候吗?所以这一点我觉得可能是无所谓,不过也许还有其他指针转换的问题???此外就是核心实现是依赖于一个shared_ptr固有调用enable_shared_from_this特定方法来把它的weak_ptr存储当前的shared_ptr里的指针,这个方法没有内线是没有办法做到的。mission impossible!
    总之这个是一个非常高级的只有特定场景的特定需求,使用的方法也是非常的特别的。
  3. 以前一直没有意识到any是不光能够匹配任何类型,更是存储任何类型的实体,从前我一直以为它是一个万能类型的元编程的东西,看来搞混了。它的核心就是一个存储的union取的一个void*指针及其align需要的最大的存储空间。当然这个是如果存储的是简单的一个可以直接初始化的,但是为了保存类型信息也是要额外的存储,还有就是所谓的创建参数,
  4. 对于any的最后一种constructor我想了好久才想到它的应用,比如我们有这么一个简单类 那么普通创建一个any可能就是这样就行了: 可是假如我们需要创建这么一个集合同时需要给它配上一个比较的方法lambda要怎么办呢? 那么我们需要怎么才能创建这个any并且初始化一些数据呢? 注意这里如果我们不给编译器明确的提示这个是initializer_list<A>的话,编译器是无法推理出我们要使用第六种创建方法的。
  5. 在cppreference里添加了我的例子代码
  6. bind和placeholder我始终不习惯,脑子转不过弯来,不如使用lambda来的简单。这里有一个例子教你自己制作自己的placeholder看起来也不难就是直接继承自integral_constant并且作为is_placeholder模板类的参数。准确的说是is_placeholder对于我们的类的特例化。具体机制我觉得肯定不简单,当初刚开始接触boost的placeholder闹了无数的笑话,感觉这个机制针对复杂的语法有些无能为力似乎问题很多,后来接触了lambda觉得完全没有必要使用placeholder了吧?
  7. 长久以来我在evince里总是遇到不能创建文件夹的权限问题,虽然隐隐约约知道大概是apparmor之类的问题但是苦于无法解决。今天终于找到了这个
    
    $ sudo apt-get install apparmor-utils
    $ sudo aa-complain /usr/bin/evince
    
    但是我随后才意识到这个实际上是破坏了安全规则,等于是允许记录在案的宽大,并不是一个好对策。所以这个反而是更好的。实际上就是在这个文件里添加额外的主目录:/etc/apparmor.d/tunables/home.d/或者更简单的是直接运行sudo dpkg-reconfigure apparmor就可以直接添加你的目录。

二月十九日 等待变化等待机会

  1. 再次学习to_chars,顺便给cppreference增添了新的例子。我觉得我从这个例子能够学习到很多,第一是lambda对于参数不定的函数的时候auto的强大。其次,是对于std::errc的转换问题,使用make_error_code的时候我忘记添加std前缀结果编译没有问题,我才意识到这个是所谓的(Argument-Dependent-Lookup)ADL的功劳,因为参数std::errc是定义在std名字空间,所以编译器会主动去那里寻找函数名字。当然这个是无法适用于像cout这类函数是依赖于操作符<<的。再就是原来例子的作者展示了使用structured capture的技巧以及在if-statement里的初始化的代码写法的高超技巧,这样子声明的局部变量的作用范围都是在if-statement内,保证了代码的优雅和安全性。尤其是很多lock/mutex是这么声明的优势就更是无与伦比的。 使用make_error_code转换得到的错误信息是很不错的:Value too large for defined data type
  2. 遇到一个问题,比如 这里的operator=的签名是什么? 这里真的是很艰苦的探索,就是函数签名因为variant有两三个operator=的重载,你简单的取函数地址不行,因为编译器说不知道是哪一个你需要,所以,你只能static_cast,当然这样子你不需要知道函数的签名,可是我的目的就是发现它,我都知道了我还要这么做吗? 注意哪怕是noexcept也是函数签名的一部分。 所以,我最终找到了原因,因为operator=是一个模板成员函数所以,要解决重载必须增加模板参数
    &std::variant<int>::operator=<int>
    我之所以这么大费周张就是想通过继承variant来添加一些方法,结果发现我无法调用原本继承来的variant的operator=方法,这个让我百思不得其解,这个方法是public的为什么我继承来的没有办法调用呢?经过艰苦的元函数的debug才意识到这个函数的返回值有大量的concept元素限制,而其中的一些看似简单的元函数被定义为variant的private的类型,比如__accepted_type,这个导致我的继承的类是无法解析从而无法调用继承来的方法了!这个是一个绝妙的范例,就是说你通过一些concept元素的accessible来限制别人继承你的方法,尽管你因为用户需要必须把它放在public里,所以,c++通过不使用Java一类的语言的关键字final来达到了某些防止滥用的目的,而且是方法一级而不是一概不准继承的粗放手段!
  3. 一个早晨就做了这么一点点的尝试。我之前是打算为from_chars写一个例子用到一个获得类型名字的比较好的做法: 而这个是配合测试的lambda: 但是这里意义不大的原因是用户需要指定自己打算从字符串了得到什么类型的结果,那么获得类型的意思就不大了。我本来想要继承自variant写一个能够conversion operator来自动获得其中的类型的方法,但是继承的方法是不全的,比如operator=是不能够继承来的,所以,意义就不大了。
  4. 给exchange加了一个例子
    这里是展示了一个意思就是一件简单的累加的工作如果让多个人来做反而是欲速则不达,多个线程反而拖累整体工作效率经常把累加的工作倒退耗费比正常多好几倍的工作。

二月二十日 等待变化等待机会

  1. 所以,locale是可以根据语言以及国家,比如同一个国家有多种语言。而locale之下有多种所谓的facet,比如是message方面,在message方面可以使用多种不同的catalog,这个catalog通常是各种不同程序创建的所谓的.mo文件,比如sed或者geany都有创建它们各自的message catalog,那么在这些catalog往往能够找到某些特定的message事先翻译好的针对语言的message数据。这个就是这个例子所表达的意思。
  2. 我尝试getpass但是推荐的是这个替代方案,可是我一直出错说Bad file descriptor让我百思不得其解,后来才明白我要传入的是stdin,而不是stdout,但是这个能够怪我吗?作为password函数你要显示prompt,就需要stdout,我以为这两个动作是都在一起完成的,真的是可笑。总之,窗户纸一捅就破就没有什么悬念了,可是在这之前还是让人感到神秘莫测,再加上我的IDE的集成的显示是一个特殊过滤的输出和实际的总不太一样,让我又有更多的困惑。
  3. 练习condition_variable

二月二十一日 等待变化等待机会

  1. 惊喜的发现大侠把我的例子代码改的好看多了!首先当然是我后来才意识到使用osyncstream的简化,作为例子不要让读者被额外的东西干扰。其次,就是一个小细节,作为if-statement拥有了初始化的能力能够声明临时变量,那么我就想当然的想要while-statement,可是没有!很显然的大神们认为while已经太复杂了不应该滥用,而且你可以使用f0r-statement,为什么要while?这个就是思想。
  2. 这个关键字__is_constructible我总是忘记,它是编译器的实现不是type_traits能够实现的。
  3. 这个是一个相当耗费时间的基本常识的再发现!
    1. 我对于Implicitly-declared copy constructor一知半解,以为这样子就省事了,可是其中的机制是什么呢?为什么编译器能够替你做这件事呢?
    2. 要知道能够做什么实际上也要知道不能做什么,什么时候你不能声明或者说实现你的copy-constructor呢?因为引进了default,我对于Big Three的概念变得模糊了,似乎可以不受这个原则的约束了 如果有一个我们不允许copyable的类,比如:
      
      struct NoCopyConstructor{
      	NoCopyConstructor(const NoCopyConstructor&)=delete;
      };
      
      那么当我们把它作为成员变量的时候它是不能允许你声明你的default copy constructor的事实上你根本没有办法使用它,因为你删除了它的copyconstructor又不声明constructor导致它根本无法使用!你要们就都不声明,要么最好都声明!
      
      struct A{
      	NoCopyConstructor noCopy;
      	A(const A&){}; // won't compile!
      };
      
      这个低级错误让我抓头,因为我被误导以为编译器产生的default的copyconstructor要求每个成员都必须要有copyconstructor,因为这里说的是
      If no user-defined copy constructors are provided for a class type (struct, class, or union), the compiler will always declare a copy constructor as a non-explicit inline public member of its class. This implicitly-declared copy constructor has the form T::T(const T&) if all of the following are true:
      • each direct and virtual base B of T has a copy constructor whose parameters are const B∧ or const volatile B&;
      • each non-static data member M of T of class type or array of class type has a copy constructor whose parameters are const M& or const volatile M&.
      这里的关键是深刻的领会,因为表面上看似乎我理解了实际使用就错了:
      
      struct NoCopyConstructor{
      	NoCopyConstructor(const NoCopyConstructor&)=delete;
      	NoCopyConstructor()=default;
      };
      struct A{
      	NoCopyConstructor noCopy;
      };
      static_assert(!is_constructible<A, const A&>::value); // do you know why?
      
      这里为什么A的copy-constructor不存在?或者说为什么这个编译会失败:
      
      A a;
      A other(a); //compilation error: use of deleted function  NoCopyConstructor::NoCopyConstructor(const NoCopyConstructor&) 
      
      原因当然很简单这个是编译器为你自东产生的,如果编译器看到成员noCopy没有copyConstructor,自然就不会为你产生了。
    3. 那么你增添A(const A&)=default;行不行?当然不行!因为这个和implicitly-compiler-generated没有什么区别!这个就是我之前理解的误区,我不明白这个default究竟有何意义?如果我不写就不同吗? 编译器产生的copy-constructor是什么?答案是编译器不知道要怎么产生,所以就不产生了!也就是说有default和没有是没区别的。
    4. 那么我就不能自己产生吗?当然可以这个没有什么不能的
      
      struct A{
      	NoCopyConstructor noCopy;
      	A(const A&){};
      	A()=default;
      };
      
      就是说编译器只是自动产生不了不代表你不能自己定义。
    5. 我为什么要折腾这个简单的问题呢?因为我遇到了另一个比较复杂的问题就是使用brace-initialization的时候的converting constructor是否应该被调用的问题,原则上应该,但是我犯了以上的错误导致不能:
      
      struct A{
      	NoCopyConstructor noCopy;
      	A(const A&){};
      	A(const char*){};// cannot use =default;
      };
      vector<A> v{"A", "B"};
      
      所以,这里我想使用converting constructor,这个是不可能有default的了!这个编译器怎么可能帮你做呢?我肯定是糊涂了。而且,这个既然是converting constructor,你的参数就不要再指望编译器帮你做conversion了,也就是说const char*到string的参数转换是不可能发生的
    一个早上我原来是打算写一个shared_lock的练习结果偏移到了十万八千里,我完全忘记要干什么了。这个就是基本功不扎实每一步都遇到地雷障碍的结果。累死了。
  4. circle真的是next-generation c++compiler吗?

二月二十二日 等待变化等待机会

  1. 使用shared_timed_mutex有些复杂因为我的程序退出但是似乎线程没有杀死。还遇到一个很可笑的问题,就是我因为default copy constructor不能使用就做了一个空的,结果发现没有任何成员被i复制,这个不是正常的吗?我却没有想到。我没有想到也是有原因的,因为我没有想到vector的brace-init-list还应用到了copy constructor: 我原本认为vector{}是一个只有constructor的过程,也就是converting constructor,实际上这里的确是有converting constructor,但是添加到vector里是通过函数参数copy,具体是哪一个函数我不确定,但是这里的教训就是我把vector{}的brace-init-list当作是aggregate initialization。实际上它是调用了内部的所谓的uninitialized_copy,这个实际上就是一个事先分配内存然后把一个个做new产生的元素拷贝,注意这里的new并不是在heap里再去分配内存而是在之前的一个总长度分配内存或者说是使用vector自带的alloc分配一整块内存然后把一个个元素拷贝到那里。这就是为什么copy constructor被调用的原因。

二月二十三日 等待变化等待机会

  1. 头脑混沉沉的一个早上都在为自己的粗枝大叶付出代价,首先是try_lock这个函数返回值成功是-1,而其他值代表失败的那个锁,而我想当然的以为返回值应该是bool,结果当然是死锁,甚至连我的破笔记本也死锁了。其次,我总是犯一个基本的错误,就是rand函数作为的初始化,可是我把变量初始化放在了for-statement结果导致随机数就只有第一次,导致我的程序有时候死锁有时候又没有问题,这个才让人抓狂,因为不确定性让我对于很多都开始怀疑了。第三,我其实是想验证一个所谓的named requirement,就是这个Lockable我感觉这个是类似一个concept,但是似乎又更加的有内涵不是说这个方法lock/try_lock/unlock支持就可以了,还要达到其中的内涵。但是无论如何我认为我的类包含一个mutex是能够达成这个要求的吧? 这么做的好处是我可以把我自己的类封装成一个lockable的对象,但是我因为遇到自己犯下的低级错误反而无法验证这么一个简单的道理。
  2. 这个是一个有意思的谜题。通常我的印象是模板参数大部分是类型,你无法传递变量,可是现在非类型模板参数扩展了很多。我做了一个类似的 这个是简单的测试

二月二十四日 等待变化等待机会

  1. 这个例子最让我震惊,我感觉学习c++以来经常遇到颠覆性的冲击,就是对于最最基本的语法都产生怀疑了。看来我还是太天真幼稚了,可能最最基本的基础没有打牢所以总是感到困惑。就是说我从来印象中变量名的选择需要避免和类型名字冲突,可是这个仅仅是你的责任并不违法!编译器居然并不限制,结果就是导致类型名被遮挡了,这个给了你一个空子可钻,如果你不喜欢某个库或者谁定义的类型你就随便命名一个变量把它遮盖起来,这个也许是行家里手心知肚明的小技巧,可是对于我如此循规蹈矩二十年真的是晴天霹雳,简直天都要塌下来了!我单单知道冬天会冻死人,可是我怎么不知道春天也会冻死人呢?
    
    struct A{}; struct B{};
    A B; //I don't like B any more, no B is allowed! 
    B b; // won't compile now! How can I use B from now on?
    
  2. 对于这个例子我还是细思极恐,为什么jthread可以接受额外的参数我却从代码里看不出来呢?比如我定义了两个函数
    
    void f(int value);
    void f(std::stop_token stop_token, int value);
    
    对于jthread来说随便哪一个都可以,可是thread却只能接受第一个。就是说jthread是可以选择性的传递一个参数给线程函数,这一点文档里也是有说明的,可是为什么代码里看不出来呢? 才找到代码,之前压根不理解所以看不懂,现在理解了就迎刃而解了,在jthread里内部实际上就是创建一个thread,只不过传参数的时候它判断一下是否线程函数支持第一个参数是stop_token的还是不支持: 所以,jthread给了一个灵活性,至于说如何运用这个stop_sourcestop_token是什么关系就是下一步的了。
  3. 感觉这个就是一个乾坤大挪移的修炼,平常资质如果修习一定时限还不能达到一定水平就说明不宜再修练,就好比围棋水平不在二十岁成为国手就此生无望了,c++的语法和库对于我来说经常感觉无穷无尽每天都像重头开始,也许是修习太慢的缘故,记忆力衰退也是大问题吧?
  4. 遇到了一个很普遍但是很不容易的问题Error: class template partial specialization contains a template parameter that cannot be deduced,这个需要细细琢磨,我目前还只是理解到non-deduced context,大概就是类型出现在成员变量这种情况是不能一一对应的。这个问题相当的不容易,我只能理解到这一点。终于找到一线曙光,这个非常的晦涩难懂:

    During template argument deduction everything on the left-hand side of the scope resolution operator :: in a template argument of the partial specialization is a non-deduced context, meaning that a template parameter appearing there will not be deduced from the corresponding argument of the specialization.

    Further, if part of a qualified type name is non-deduced context, then all parameters used to specify the type are non-deduced.

    所以,这个就是我面对的难题这个没法编译啊? 我感觉有一点点开窍感觉大概的方向就是要避免直接使用带scope::的类型,这个需要脑筋体操绕过去,这个不容易啊。
  5. 我想了半天就睡着了。醒来之后做了一个小函数,不知道这个是不是有意思? 那么对于这个函数你可以做什么呢?它可以作循环,比如 你可以呼叫四次f得到9。当然也可以做流水线来加工
    
    foo([](auto i){return sqrt(i);}, [](auto i){return rand()%100+i;}, [](auto i){return i*i;}, 5);
    
    实际上这个等价于fold expression,因为它本质上是这个foo(f(f(f(f(5)))));其实还是有本质区别的因为fold expression是一系列的数据对应于一个操作的形式,而我的表达式是一个数据对应于多个操作符的。
  6. I rest my case.就是说你没有可能从invoke直接调用constructor,之前我对于这个原因还不是很清楚,现在看了这个说明才真正有些理解,比如我们平常调用成员函数的时候是要传递this指针的,这个只能是从其他实例来,那么同样的这个实例从哪里来呢?如果你已经有了何必要再费力产生? 那么调用成员函数是 这里肯定是特殊的处理了,因为construct_at之类的使用new分配地址在指定内存是绕过去了。 注意这里的aliagnas(A)很重要,很多非x86架构的内存地址是有限制的,当年遇到ARM上就是指针8byte对齐的。而这里我一开始使用static_cast是通不过的,reinterpret_cast似乎是和c cast类似吧?

二月二十五日 等待变化等待机会

  1. 昨天做的那个compose operator有什么意义呢?这个是我自己起得名字: 至少在我看来可以做一些运算的各种排列组合来检验最佳的效果,比如哪怕是简单的加减乘除按照不同顺序执行结果也是不同的,当然这个都是废话否则干嘛需要操作符优先级呢,如果效果和顺序无关的话,我目前缺乏一个产生类似于next_permutation_sequence的元函数来配合:
  2. 写了一篇小品文

二月二十六日 等待变化等待机会

  1. 似乎clang13对于make_shared的支持有问题,提了一个bug
  2. 编译器真的遵守规则吗?因为先入为主的观念阻止了一些看似合法的specialization。比如编译器不认为int[0]是一个array,所以,is_array的判断被无视了。 你认为编译器是否越俎代庖的在my_is_array<int[0]>就不承认,因此导致你的specialization根本就不成立。
  3. 这个Arrays of Length Zero并非是无用的!我感觉这个也许是一个bug吧?我也不是很确定。
  4. 写了一篇小品文
  5. 文章草稿会丢掉,只好发表尽管写的不好。
  6. 发现有人早就发现了我的发现: 问题是这样子的uniqe_ptr就会报错,而make_shared并无此要求。所以,我不是很有把握的提了这个bug
  7. 我也同时提交给clang因为我现在才意识到这个实际上是库的问题,clang只是照搬库,除非clang自己修正库,我很怀疑。或者这个是否是bug呢?
  8. 又写了一篇小品文,基本上就是把之前的笔记改了一下而已。

二月二十六日 等待变化等待机会

  1. 写了一篇小品文,运用CRTP解决了之前的障碍成功的能够自东实现lambda的功能。 于是我们可以轻松的实现functor到函数指针的转换。 这个是如何运用 完整的代码
  2. 看到一道题目,让我感到意外的是关于constructor被打断之后为什么会再次执行呢?
    1. 首先,一个类的成员是在它的constructor之前就要初始化的,如果在这个过程有异常就是说constructor从来没有被呼叫。
    2. 在一个函数里的static变量肯定是只会初始化一次的,它不是stack变量,问题是当第一次初始化被打断再次呼叫整个过程能够被再次调用,也就是说一个类的初始化过程似乎有一个标志量编译器知道它是否完成了,而这个是运行期的行为,编译器怎么知道一个类是否已经初始化了?
    3. 我尝试在类的construtor添加noexcept结果导致外围的try/catch完全不起作用。这个是c++语言的设计,它能够让所有相关的try/catch无法捕捉异常,听说是stackwinding的代码不产生,这个部分还是很复杂的。就是说捕捉要依赖于额外的代码才能实现。是否每个函数都会有这样子的代码呢?
  3. 另一个帖子里是很典型的converting constructor的问题。对于一个constructor如果有explicit则converting constructor是不能允许的。copy constructor似乎始终是自东产生的,所以并不用担心。

二月二十八日 等待变化等待机会

  1. 大师的耐心细致的解说
    1. 我学到了一个很有用的技巧,就是size_t实际上不是编译器built-in的type,就是说POD里没有它需要库来定义,那么如果你不想依赖于库要怎么做呢?自己定义!
      
      using size_t=decltype(sizeof(0)); // no dependency for any library!!!
      
      大师这里也说明了int[0]不是当作数组,这个是编译器的行为,和库无关 这个的确是我的观点,但是大师认为没有必要库对于编译器的行为做补丁,根本无必要,大师教会我一个英文单词kludge,就是这个意思,费力去拼凑原本就不是很好的空架子。中文里实在是很难翻译。

      大师教导我llvm的库并非标准的gcc用的库,它们使用的是libc++,它们自己有自己的实现。这个是点醒梦中人,我以前一直以为clang是默认直接调用gcc的库,为了兼容性?这个部分我还是很不清楚,应该说即便是需要编译的动态库部分不同而且需要编译,即便是头文件部分不需要编译的也有自己的实现,只是兼容性有更复杂的内容。我对此一无所知。以后留心看看。

    2. 关于libc++:首先,它是另起炉灶了,GCC用的是libstdc++,Apache用的是libstdcxx。它的创立的理由是兼容和效率的矛盾选择了后者。string使用内部的short string optimization,这里似乎使用Copy-On-Write(COW)对于多核CPU是更加的有效率。
    3. STLport是什么呢?似乎是私有公司但是开源跨平台的实现。关于libc++的原因还有一个就是许可证的问题,我对于此始终搞不明白GPLv2/3的区别。似乎焦点都是是否支持c++11看来对于更加高级的语法还是在开发中。
    4. 我终于找到以前看过的帖子,我是点过赞的现在一点印象都没有了。结论是没有必要使用libc++因为libstdc++是主流大家都要去兼容,何必要另起炉灶?我唯一能够解释的就是llvm似乎选择性的用libc++的一个支持c++11的部分其他还是用libstdc++吧?不知道这个猜测是否对?
    5. 大师的话需要细细研读,因为理解起来很不容易。
      int[0] is neither an unbounded array (like int[]) nor an array with non-zero bound (like int[1])
      说白了它不是数组。即便我们能够创建shared_ptr<int[0]>你能用它做什么呢?实际上是无法用make_shared创建的因为会引发pseudo-destructor失败。因为它被当作类来处理的。因为编译器不认为它是数组所以,is_array也遵循这一点。在这一点上库是遵循编译器的,要和核心保持一致。那么这么一个空无一物的东西有什么用呢?我认为基本上是无用的。也许它可以夹三儿在类的成员里不占空间。但是不要忘了它名义上还是以int出现会影响alignment的。注意没有#pragma pack(1)的情况。
      
      //#pragma pack(1)
      struct B{
      	int empty[0];
      	char ch;
      };
      static_assert(sizeof(B)==sizeof(int));
      static_assert(sizeof(int[0])==0);
      
    6. The restrictions on C99 flexible array members are relevant here:
      • - Flexible array members have incomplete type, and so the sizeof operator may not be applied. As a quirk of the original implementation of zero-length arrays, sizeof evaluates to zero.
      • - Flexible array members may only appear as the last member of a struct that is otherwise non-empty.
    7. 结论就是使用make_shared/make_unique类型是unbounded-array配合参数0是可以达到分配一个空指针的目的的。它们都是一个指针指向一个size是0的空间。
  2. 我的怀疑是对的,声明为static的函数的变量在constructor的确是有一个锁的机制,这个是相当的复杂的。这里有一个__cxa_guard_acquire__cxa_guard_release作为一个锁定和释放的机制,据说是防止递归多次初始化的问题。作者设计了一个导致crash的递归调用,应该在GCC不是问题吧?不!也会crash。总之非常的复杂,我不打算再看了,因为非常的复杂。还有一点就是static居然是在.data而不是在.bss的,所以,它是在函数调用的时候才初始化,而不是在main之前初始化。这个大侠gdb里使用info addr variable-nameinfo symbol variable-address就能够显示变量是在哪一个section的了!
  3. 这个是关于abi的资料的网站我应该下载保存一下以前保存过了。,这个是一个关于psABI exception handling,这个是实现范例

三月一日 等待变化等待机会

  1. 总算做对了一道reverse Integer的题。
    Given a signed 32-bit integer x, return x with its digits reversed. If reversing x causes the value to go outside the signed 32-bit integer range [-231, 231 - 1], then return 0.
    
    class Solution {
    public:
        int reverse(int x) {
            int upper=numeric_limits::max()/10;
            int upperDigit=numeric_limits::max()%10;
            int lower=numeric_limits::min()/10;
            int lowerDigit=numeric_limits::min()%10;
            int result=0;
            for (int i=x; i!=0; i/=10){
                int digit=i%10;
                if (result>upper || resultupperDigit 
                    || result==lower&&digit

三月二日 等待变化等待机会

  1. 如果不利用index_sequence的特点二盲目的使用递归实现基本功能就太傻了!比如我一开始使用递归实现一个简单的get_index_N 且不说这个是递归函数,单单对于不利用数组随机访问的优势就是不能接受的。我们首先定义个对于数列的包装类 然后访问就转化为数组的访问了 很有趣的是两个不同的实现可以并存,我一时之间无法分辨到底调用的是哪一个,我怀疑这个是模板函数的另一个特点,失败的那个就静悄悄的被放弃,但是两个函数返回值如果不同呢?
  2. 写了一篇小品文
  3. next_permutation还是一个相当复杂的算法。我花了差不多半天时间来实现,因为困难的是这个是元计算,debug是完全不同的风格,只有靠static_assert。
  4. 写了一篇小品文,太长了我懒得解释。

三月三日 等待变化等待机会

  1. 对于constexpr的理解还是有偏差,返回值是constexpr并不是要求函数里所有的变量都必须是constexpr,这样子就没有什么意义实现函数了。但是对于一些选择支的确是需要的,因为有很多的递归和模板在这些选择支里是被过滤掉了,否则很多的模板都是递归直接就溢出了。找着这个例子去实现lower_bound/upper_bound本来是照方抓药一样抄写,但是我对于算法感到头疼始终写的不对,还导致无限递归让我错以为是constexpr的问题。 对于upper_bound我对于定义理解有偏差导致操作符选择就出错了 否则两者算法代码一模一样。
  2. 有了lower_bound,实现binary_search就是小菜一碟了: 这里的教训就是返回值是constexpr并不要求函数内部所有的变量需要是constexpr。这个是常识,但是为什么我会有这个多余的疑虑呢?
  3. 写了一篇小品文
  4. 看到一篇关于cast的文章:
    When you use static_cast, by defaut (i.e. without optimizations activated) it calls the conversion constructor of the object you are trying to cast into (if it exists).
    这一点我已经体会到了因为当我为了实现conversion constructor而试图用static_cast来实现就发现是死循环。
    Talking about conversion constructors, they can be transitive when static_cast is used.
    传递性!作者的例子还是让我有小惊喜,这个链条可以很长很长!错了!我还没有看完就被打脸!不是传递性!仅仅是。。。In fact, static_cast is not transitive
    The expression static_cast<FooBar>(foo) tries to call the following constructor: FooBar(const Foo&). However, it doesn’t exist, the only conversion constructor FooBar has is FooBar(const Bar&). But, there is a conversion available from Foo to Bar, so the compiler implicitly converts foo into a Bar to call the FooBar(const Bar&).
    这里要细细体会。就是说要揣摩编译器的心思,究竟conversion是怎么发生的,编译器首先去找被cast的对象的constructor,如果找到了再看看参数能不能匹配,不能匹配就看看能不能转化,所以才牵动了参数的conversion constructor,所以,并不是直接调用了conversion constructor而是它的参数的conversion。这中间的细微诧异让人出了一身的冷汗。
    Implicit conversion can happen anywhere. Since static_cast (and any cast) is, pragmatically3, a “function call” (in the sense that it takes an argument and returns a value) it gives two opportunities for the compiler to try an implicit conversion.
    要把static_cast看作是函数!这个是非常让人震惊的。

    作者接着谈到了我始终模糊的地方C-style-cast是怎么回事?

    1. First, they can convert one scalar type into another.
    2. Second, they can be used to reinterpret a pointer into a pointer of another type.
    3. And finally, it can be used to add or remove a const qualifier.

    这里是重点!总结:

    So in C++, when you do a C-style cast, the compiler tries each one of the five following cast operations (in that order), and uses the first that works:

    • const_cast
    • static_cast
    • static_cast followed by const_cast
    • reinterpret_cast
    • reinterpret_cast followed by const_cast
    这里的详情我也看不下去了,因为这个研究几个月都不一定十分清楚。

    大佬给出了总结:

    Why this is actually bad?

    Most C++ developers agree that it is really bad practice to use C-style casts in C++. Here are the reasons why: it is not explicit what the compiler will do. The C-style cast will often work, even when there is an error, and silence that error. You always want only one of these casts, so you should explicitly call it. That way, if there is any mistake there’s a good chance the compiler will catch it. Objectively, there are absolutely no upsides to using a C-style cast.

    用我自己的话就是C-style-cast就是小孩子哭闹不讲理一定要,要什么也不说,反正就是要。而c++ cast要说清楚具体要什么。小孩子不说清楚很多时候总是成功的因为大人(就是编译器)不敢不给,就算是明知道小孩子错了出于爱护程序员尊重程序员的智慧理智还是给了,结果错了也不能怨编译器。而c++cast说清楚了编译器帮你分析可以很确定的给出结果,不行就是不行不能眼睁睁看着你往火坑里跳。

  5. 这个博文网站还是值得以后来看看的。主要是很翔实注释来源很细致,好像专业的paper一样严谨。
  6. 对于实现这类很便宜的小函数all_of/any_of/none_of,find我有时候觉得真的有些无语
  7. 想像中很美好,落地才发现似乎不可能。我之前设想的compose函数使用一系列的函数作为参数,但是那个基本例子都是非模板函数而且参数是一个很简单的整数,所以,当我满怀信心想要使用模板参数作为compose的参数时候才意识到根本不可能。
    
    constexpr auto seq=compose(next_permutation_sequence,index_sequence<1,2,3,4>{});
    
    这里有没有可能进行模板参数的推导(Template Argument Deduction)呢?且不说这个是比登天一样难,而且还没有开始就发现这个似乎是死路一条。标准明确说这个不予推导!End of story!
    The parameter P, whose A is a function or a set of overloads such that more than one function matches P or no function matches P or the set of overloads includes one or more function templates.
  8. 所谓的Class template argument deduction从来就是关于模板类的,和模板函数无关!尤其是关于模板类的constructor,因为这个是最根本的问题。所以,我根本是做梦的。不可能的。我甚至都没有想清楚我这个next_permutation_sequence要怎么循环使用呢?它是一个模板函数,传入的参数是一个模板类,返回是另一个模板类,我怎么可能创造一个变量接受这个返回的不同类型的模板类变量再作为参数代入这个函数呢?它们是把不同的类型,除非我变为指针?可是这个是模板类啊,是类型。。。不可能的。

三月四日 等待变化等待机会

  1. 感到迷茫。下载了这本书《c-template-metaprogramming-concepts-tools-and-techniques-from-boost-and-beyond_compress.pdf》,也许能够指明一些方向。
  2. meta programming的定义是什么?
    If you dissect the word metaprogram literally, it means "a program about a program."

三月六日 等待变化等待机会

  1. 感到迷茫。
    1. 对于lambda我始终纠结于两种细微的差别,究竟哪一个更好呢?是否可以定义一个模板lambda类然后让它的成员函数operator()自动获得模板能力,还是大家通常的做法是针对lambda的成员函数定义模板成员函数呢?
    2. 说到底lambda就是一个匿名的functor,那么现在LambdaTemmplateClass就是一个匿名的模板functor类。
    3. 而这个是一个lamda的成员函数也就是operator()是一个模板函数。
    4. 从使用的角度来看似乎差别也不大,比如如果直接调用的话,前者是一个类型需要实例,后者已经是一个匿名的独一无二的实例了。 输出的结果有些微的差别,因为前者是需要模板参数才能创建实例,而后者本身就是一个实例,只不过它的成员函数是模板函数需要参数的指引,但是编译器足够聪明可以从函数参数直接推导模板参数:
    5. 如果两个都作为其他回调函数的回调函数来使用呢? 输出的结果和直接调用没有任何差别。
    6. 如果把它们作为函数指针来使用呢? 这个也是没有差别的,因为两者都可以直接被转换为函数指针,相当于:
    7. 可是我如果想直接启用operator+两者就有差别了。 对于后者你无法显式的调用它的operator+编译器会报错说操作元无效之类,因为你实际上没有指明你到底要的模板成员函数的模板参数是什么?或者说+lambdaMemberTemplate相当于要返回了一个模板函数的指针void(*)(T)这里当然是模拟说明是不明白T到底是什么,所以,编译器毫无例外的报错。。
    8. 这里有一点点额外的就是这两个定义是等价的,两者没有什么区别。
    9. 而我为了尝试msvc++发现它的宏更加的丰富一些,如果使用__FUNCSIG__就是函数签名的话,你会发现msvc++的调用远比Linux来的复杂,单单这个__thiscall和__cdecl的区别就够烦人的了,我估计这个是中间层链接的复杂地方。还好我讨厌windows平台。
    这个说明了什么呢?我也想不清楚。
  2. 写了一篇小品文

三月七日 等待变化等待机会

  1. 这里有一些meta函数的小玩意,也许以后可以参考。

三月十三日 等待变化等待机会

  1. 对于binary tree的递归练习还是有帮助的,最起码练习一下递归编程的基本要领,其实还是基本功就是结束条件和到底如何递归的问题。
  2. 这里的这道题目就不简单,我是卡壳在这个算法的优化上了,因为传统的递归是无法应付大数据的。我之前对于palindrome字符串的判断上没有深刻的意识到它是一个递归的过程,实际上就算我看懂了算法也不明白它为什么是这样子的。总之,我连个人的体会都写不出来。这个到底是算什么算法呢?动态规划?还是说通过二维矩阵把一维的字符串赋予了透视的能力就解决了?用空间去换时间肯定是没有错的,问题是怎么做到的呢?用二维寻找到了一维的捷径?使用递归来避免反反复复的重复计算?因为palindrome说白了就是首尾两个字母的比较代替了中间所有字母的逐个比较,这个就是建立表来避免重复递归计算。但是究竟是怎么做到的呢?我脑子不清楚的时候甚至不知道要写下什么感想,现在明白了一点,就是说它的优化在于利用了palindrome递归的属性,首先我们建立一个字符串里所有的palindrome的过程是从长度1开始逐步增加的,而每次我们都是在之前已经判明的palindrome的基础上扩展,每次我们只是依靠仅仅判断当前长度的子串的首尾是否相同再加上之前长度的palindrome的辅助就够了,所以,它省却了我的傻傻的每次都运行标准的判断palindrome的小函数,这个就是重大的优化。这个思想就是几乎所有避免重复递归的要义:假如你的递归是依赖于之前的结果的话不如把每次的结果都从小到达保存这样你就只要计算很少的部分依赖于上一次递归的结果来加快运算。这个累计的过程就是优化的过程。
  3. 核心是怎样设计一个二维数组来表现一维数据的特性,这个是算法的核心。用高阶数据结构来表达低阶数据结构是一个高深的算法的核心!而高阶数据结构的复杂度就是算法的复杂度,所以,如果能够提前知道算法复杂度就可以引导设计这样复杂度的数据结构来帮助优化算法,当然前提是你已经知道优化的算法的复杂度,所以,这个往往是不可能的。

三月十五日 等待变化等待机会

  1. 在这位大侠的书《c++ lambda story》这本里提到了另一位大侠,这个cppinsights给了我及其深刻的印象:他是怎么做到的?!简直是不可思议!反编译吗?这里是让我最感到震撼的一个例子就是说lambda是如何实现:
    
     class __lambda_4_18
      {
        public: 
        inline void operator()() const
        {
        }
        
        using retType_4_18 = auto (*)() -> void;
        inline operator retType_4_18 () const noexcept
        {
          return __invoke;
        }
        
        private: 
        static inline void __invoke()
        {
        }
        
        
      } __lambda_4_18{};
    
    感谢上苍,这位大侠开源了他的代码,这个很可能是我追寻了好几年想要达到的效果吧?让我赶紧下载看看!
  2. 大侠的说明让我明白大体上他是手写的,这个也许并没有非常完美,但是起码给了我启示,就是使用clang作为工具这个似乎是唯一的方向!然后一个好的引路人并不是帮你走完道路而是给你指引道路,所以,我又看到了这个开发工具cevelop这个也许是另一个途经!因为这个也许就是eclipse里使用的那个我一直不知道来源的插件吧,这也是一个很好的机会看看别人是怎么做的,更主要的是能够解决我的eclipse的问题就是最最有意义的。
  3. 这又是一个盲目的搜索,首先我枪毙了clion,因为要钱,同样的qt creator也是一样。sublime text也是要钱。eclipse并不是原生的c++项目它和clangd的结合不知现在如何?这篇文章是2017,现在呢?
  4. 大侠给了我一闷棍因为成员函数是不用担心nullptr指针的dereference的,除非要访问成员数据。

三月十六日 等待变化等待机会

  1. 折腾了好久总算是编译通过了,我使用了一个很简单的Makefile来编译,因为事实上大侠的代码文件并不多,主要就是链接问题,这里是一个普通的练习
    
    LLVM_PATH=/mnt/sda2/Shared/llvm-dev/build-shared-install
    INC_PATH=${LLVM_PATH}/include
    LIB_PATH=${LLVM_PATH}/lib
    SRC_ROOT_DIR=${PWD}
    OBJ_ROOT_DIR=${SRC_ROOT_DIR}/objs
    DYNAMIC_CLANGLIBS=$(notdir $(wildcard $(LIB_PATH)/libclang*.so))
    DYNAMIC_CLANGLIBS_FLAG=$(patsubst lib%.so,-l%,$(DYNAMIC_CLANGLIBS)) 
    DYNAMIC_LLVMLIBS=$(notdir $(wildcard $(LIB_PATH)/libLLVM*.so))
    DYNAMIC_LLVMLIBS_FLAG=$(patsubst lib%.so,-l%,$(DYNAMIC_LLVMLIBS))
    CLANG_LIBS_FLAG=$(DYNAMIC_CLANGLIBS_FLAG)
    LLVM_LIBS_FLAG=$(DYNAMIC_LLVMLIBS_FLAG)
    LIBS=$(CLANG_LIBS_FLAG) $(LLVM_LIBS_FLAG) -ltinfo -pthread -ldl -static-libstdc++ 
    GCC=/home/nick/opt/gcc-11.2.0/bin/g++
    CXX=${GCC}
    CPP_FLAGS=-std=c++20 -fno-rtti -Wno-deprecated-enum-enum-conversion 
    SRC_FILES :=  $(notdir $(wildcard  $(SRC_ROOT_DIR)/*.cpp))
    OBJ_FILES :=  $(patsubst %.cpp,$(OBJ_ROOT_DIR)/%.o,$(SRC_FILES)) 
    all: ${OBJ_FILES}
    	${GCC} ${OBJ_FILES} -o cppinsights.exe -L${LIB_PATH} $(LIBS)
    ${OBJ_ROOT_DIR}/%.o: ./%.cpp
    	$(CXX) -c ${CPP_FLAGS} -I. -I${INC_PATH} -o $@ $<	
    clean:
    	rm -fr ${OBJ_ROOT_DIR}/*.o		
    
    这里有几个小地方要注意的:
    1. 之所以选择动态库就是因为静态库遇到了依赖顺序的问题很难解决,需要很多时间去摸索。LLVM项目里有代码排序静态库依赖的工具,但是太麻烦了。
    2. 我其次,我始终遇到rtti相关的问题,就是一些typeinfo比如回调函数参数或者是子类继承等等的错误,我以前使用-fno-rtti压制了这个错误。但是现在看来也许是掩盖了更加深层次的链接问题。
    3. 所以,编译完了我运行总是遇到这个错误CommandLine Error: Option 'limited-coverage-experimental' registered more than once!我怀疑这个就是这样子粗放的把所有的库都链接造成的吧?
    所以,我遇到这个问题不知道要怎么解决,想要回到大侠的编译方法,因为看起来llvm/clang工具的开发还是有相当的复杂度的,可是我连编译clang都没有很好掌握。我决定花一个星期时间编译clang及其工具扩展,这个是必须要做的。这个理由很简单:既然我觉得clang是未来的光明之路,那么不妨现在就开始,似乎只有clang能够解决我的需求。gcc似乎是日落黄昏了。想到这里心情就轻松了,毕竟单单解决一个固定目标的小问题是不成问题的问题。
  2. 把之前的小品文重新收集在这里
  3. 我这里做了一个不太道德事情就是下载了大侠的书,但是我答应自己仅仅是浏览,借鉴纯粹是个人行为,个人收藏
  4. 这里是重新再总结的编译指令。我想再抄写一遍加深记忆,不是为了记住而是为了有印象。
    选项功能
    CMAKE_BUILD_TYPE:STRINGPossible values are Release, Debug, RelWithDebInfo and MinSizeRel应该选择RelWithDebInfo
    CMAKE_INSTALL_PREFIX:PATHPath where LLVM will be installed 这个是我设置安装路径
    CMAKE_{C,CXX}_FLAGS:STRINGExtra flags to use when compiling C and C++ source files respectively.这里我可以限制是否禁止-fno-rtti之类的
    CMAKE_{C,CXX}_COMPILER:STRINGSpecify the C and C++ compilers to use这个指定我的编译器路径,不知道是否编译会用到c编译器?
    LLVM_PARALLEL_{COMPILE,LINK}_JOBS:STRING这个直接限制链接的并行数,这个是否就能解决我的快速编译的问题呢?make -j被override?
    LLVM_TARGETS_TO_BUILD:STRING应该设置-DLLVM_TARGETS_TO_BUILD=X86这个很重要否则llvm是默认编译所有的可能的target的!!!
    LLVM_USE_LINKER:STRING我觉得默认的gcc的linker可能确实是慢,不妨尝试gold或者lld
    CMAKE_CXX_STANDARD:STRING这个默认c++14是否需要更高值得怀疑,我觉得最多c++17就够了,毕竟是编译编译器本身的代码啊
    BUILD_SHARED_LIBS:BOOL目前我看到的就是这个问题,这个是无数的动态库,可是真的需要吗?因为既然是动态库就没有可执行文件的大小的问题,仅仅是部署的动态库大小的问题,而这个问题是没有人关心的,所以,不应该使用这个而是LLVM_BUILD_LLVM_DYLIB:BOOL
    LLVM_BUILD_LLVM_DYLIB:BOOL注意不能和以上的BUILD_SHARED_LIBS连用!因为这个是把所有的动态库包装为一个!
    LLVM_BUILD_TOOLS:BOOL我怀疑我们需要打开这个开关因为cppinsights是被拷贝在tools里的
    LLVM_ENABLE_DIA_SDK:BOOL这个既然是微软专有的为什么defaults on呢?干脆关闭吧。
    LLVM_ENABLE_EH:BOOL这个我看不大懂也许打开看看
    LLVM_ENABLE_IDE:BOOL这个干脆关闭吧省的啰嗦
    LLVM_ENABLE_PROJECTS:STRING这个地方我始终有误解,我觉得我现在有点理解了因为无论你要编译什么llvm始终是要第一个编译完了才行,而目前最新的版本clang已经不是被放在llvm/tools下面了,除非你是分别checkout的,总之我觉得这个设置需要clang;clang-tools-extra
    LLVM_ENABLE_RUNTIMES:STRING这里也是我纠缠不清的究竟什么是runtime呢?我觉得不应该因为我不想使用clang的runtime
    LLVM_STATIC_LINK_CXX_STDLIB:BOOL我觉得这个很重要,它就是-static-libstdc++ 而这个对于我的高阶版本的gcc编译器很重要因为它自带的库比我系统默认的版本高,很重要
    目前这个是我尝试的一个配置命令:
    
    ~/Downloads/cmake-3.18.4/bin/cmake -G "Unix Makefiles" -DCMAKE_CXX_COMPILER=/home/nick/opt/gcc-11.2.0/bin/g++ -DCMAKE_C_COMPILER=/home/nick/opt/gcc-11.2.0/bin/gcc -DCMAKE_CXX_STANDARD=20 -DLLVM_ENABLE_PROJECTS="clang" -DCMAKE_BUILD_TYPE="RELWITHDEBINFO" -DLLVM_TARGETS_TO_BUILD="X86" -DLLVM_BUILD_LLVM_DYLIB=ON -DLLVM_STATIC_LINK_CXX_STDLIB=ON -DLLVM_PARALLEL_LINK_JOBS=1 -DLLVM_ENABLE_IDE=OFF -DLLVM_ABI_BREAKING_CHECKS="FORCE_OFF" -DLLVM_INCLUDE_TOOLS=ON -DLLVM_BUILD_TOOLS=ON -DLLVM_INCLUDE_UTILS=OFF -DLLVM_BUILD_UTILS=OFF -DLLVM_BUILD_RUNTIMES=OFF -DLLVM_BUILD_EXAMPLES=OFF -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_BUILD_TESTS=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_INCLUDE_GO_TESTS=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_BUILD_BENCHMARKS=OFF -DLLVM_INCLUDE_DOCS=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_BINDINGS=OFF -DCMAKE_INSTALL_PREFIX=/mnt/sda2/Shared/llvm-dev/dylib-install/ ../llvm-project/llvm
    
    尝试了好多次发现cppinsights可能和clang-tools-extra目标有冲突。
  5. 这个现在看不懂,但是感觉不明觉厉。以后再说吧。

三月十七日 等待变化等待机会

  1. -DCLANG_LINK_CLANG_DYLIB=on -DLLVM_LINK_LLVM_DYLIB=on看来这个是可以分别细分llvm/clang的开关!老烟昏花了!而llvm-config是llvm的tools的一个命令行工具,这个是所有依赖于llvm做开发的必选,那么关键就是配置的时候如何指示这个路径的问题了。除非我做系统安装?遇到很多的警告,不如打开这个开关压制警告-Wno-deprecated-enum-enum-conversion
  2. 最后我发现还是在llvm之外来编译比较容易,因为配置简单,比如正确安装了clang14之后,改了一个小小的clang函数参数类型升级变化的小问题后就是这样子的配置:
    
    ~/Downloads/cmake-3.18.4/bin/cmake -G"Ninja" -DCMAKE_CXX_COMPILER=/home/nick/opt/gcc-11.2.0/bin/g++ -DCMAKE_C_COMPILER=/home/nick/opt/gcc-11.2.0/bin/gcc -DCMAKE_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_CXX_FLAGS="-static-libstdc++"  ../cppinsights
    
    注意这里-DCMAKE_CXX_FLAGS="-static-libstdc++"是因为我使用了高阶版本的gcc采用c++20标准来编译的时候它自带的标准库进行静态链接省却麻烦。现在我终于可以使用cppinsights来一睹究竟了!确实是神奇。第一步编译成功之后,接下去就是来看代码学习了。编译clang确实是很费劲因为占用磁盘经常几十上百G,这个是让人很头疼的。另一个收获是发现ninja的编译似乎是比make来的高效一些吧,至少wiki是这么说的。
  3. 我这两天持续看到所谓的language server这个对于我来说还是一个很神奇的东西,其中包含了所谓的微软定义的language protocol之类的吧,这个很有可能是一个潮流吧?可是我对于此是一无所知。
  4. 透过看ninja的文件可以看到真正的链接命令参数
    
    LINK_LIBRARIES = -Wl,-rpath,/usr/local/lib: -lclangTooling  -lclangASTMatchers  -L/usr/local/lib  -lclangFrontend  -lclangDriver  -lclangSerialization  -lclangParse  -lclangSema  -lclangAnalysis  -lclangEdit  -lclangAST  -lclangLex  -lclangBasic  -lclangRewrite  -lLLVMWindowsManifest -lLLVMXRay -lLLVMLibDriver -lLLVMDlltoolDriver -lLLVMCoverage -lLLVMLineEditor -lLLVMX86TargetMCA -lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen -lLLVMX86Desc -lLLVMX86Info -lLLVMOrcJIT -lLLVMMCJIT -lLLVMJITLink -lLLVMInterpreter -lLLVMExecutionEngine -lLLVMRuntimeDyld -lLLVMOrcTargetProcess -lLLVMOrcShared -lLLVMDWP -lLLVMSymbolize -lLLVMDebugInfoPDB -lLLVMDebugInfoGSYM -lLLVMOption -lLLVMObjectYAML -lLLVMMCA -lLLVMMCDisassembler -lLLVMLTO -lLLVMPasses -lLLVMCFGuard -lLLVMCoroutines -lLLVMObjCARCOpts -lLLVMipo -lLLVMVectorize -lLLVMLinker -lLLVMInstrumentation -lLLVMFrontendOpenMP -lLLVMFrontendOpenACC -lLLVMExtensions -lLLVMDWARFLinker -lLLVMGlobalISel -lLLVMMIRParser -lLLVMAsmPrinter -lLLVMDebugInfoMSF -lLLVMSelectionDAG -lLLVMCodeGen -lLLVMIRReader -lLLVMAsmParser -lLLVMInterfaceStub -lLLVMFileCheck -lLLVMFuzzMutate -lLLVMTarget -lLLVMScalarOpts -lLLVMInstCombine -lLLVMAggressiveInstCombine -lLLVMTransformUtils -lLLVMBitWriter -lLLVMAnalysis -lLLVMProfileData -lLLVMDebugInfoDWARF -lLLVMObject -lLLVMTextAPI -lLLVMMCParser -lLLVMMC -lLLVMDebugInfoCodeView -lLLVMBitReader -lLLVMCore -lLLVMRemarks -lLLVMBitstreamReader -lLLVMBinaryFormat -lLLVMTableGen -lLLVMSupport -lLLVMDemangle  -lrt -ldl -lpthread -lm -lxml2
    
    从这里看出来我的问题可能就是LLVM的静态库顺序吧?另一个有趣但是我还没有想明白的就是version.h里定义的那些宏对于编译究竟产生了什么?

三月十八日 等待变化等待机会

  1. 老爸 09:25 赏生机盎然赞

    桃花盛开春来到, 原野风光四时好。 可怜锁居闹市人, 长恨春来没处找。
  2. 风吹浪去谈笑鸡虫 22:07 和老爸生机盎然赞

    雪花飘尽春来早, 桃花盛开尽妖娆。 心中但有春意在, 踏雪破冰春风飘。

    风吹浪去谈笑鸡虫 22:17 再和老爸生机盎然赞

    桃花妖娆春来早, 十四亿人乐逍遥。 若非一人能领导, 哪得神州尽舜尧?

    风吹浪去谈笑鸡虫 22:46 又和老爸生机盎然赞

    春风吹过桃花笑, 神州雨顺又风调。 可怜俄乌燃战火, 百姓涂炭万物凋。

    观桃花盛开有感

    冬尽春来桃花妖, 俄乌冲突战火烧。 喜剧演员演悲剧, 存亡之道要记牢。

    观桃花盛开有感

    冬去春来桃花傲, 中美关系破冰好。 俄乌战火疫情虐, 世界危局心内焦。

三月二十日 等待变化等待机会

  1. 这个问题的错综复杂超过了我的想像。
    1. 首先,令我感到吃惊的是cppinsights实际上并不是修改了编译器编译的代码,应该说是还原更加的准确,比如它只是按照AST的解释结果再输出,所以,这个才是所谓的insights
    2. 那么问题就出现了,我反复遇到insights无法找到concepts这个头文件,我一开始以为是在它的version.h配置里需要把INSIGHTS_LLVM_INCLUDE_DIR里的-isystem由LLVM的安装目录改为我的实际编译器的目录。这个说来真是话长:我使用的clang是我自己编译的,而编译llvm/clang的编译器是我自己编译的gcc-11.2.0,它们的配置目录都不是标准目录,我的理解是clang依赖于编译它的gcc找到所谓的系统目录-isystem,因为这些stl的头文件是在我的编译器自带的目录,也就是gcc-11.2.0的目录里的。
      If a standard system include directory, or a directory specified with -isystem, is also specified with -I, the -I option is ignored. The directory is still searched but as a system directory at its normal position in the system include chain. This is to ensure that GCC’s procedure to fix buggy system headers and the ordering for the #include_next directive are not inadvertently changed. If you really need to change the search order for system directories, use the -nostdinc and/or -isystem options.
      这里说的是什么呢?我的理解就是你如果犯了错重复了在用户级别的-I是无害的,而唯一想要彻底改变系统目录你只能取消系统目录搜索自己再去定义。
    3. 但是在尝试改变系统目录搜索之前先明白什么是系统目录:我之前研究了半年的preprocessor其实连这个问题也没有搞清楚!
    4. 但是在批评GCC之前你先看看clang编译后是否真的能够解决这个问题呢?我现在想尝试不要使用gcc-11.2.0来编译,毕竟使用c++20来编译编译器似乎没有什么好处。也许问题是出在这里吧。总之我现在编译的clang就不是正确的,就不用指望这个clang的结果是正确的了。
    我的感觉是这个比交叉编译还要复杂,因为这里我的概念根本不清楚,并不是简单的运行平台的问题,因为现在是编译编译器的问题。
  2. 使用gcc-7.5编译clang-13要怎么支持c++20呢?如果说clang是依赖于gcc自带的std/stl头文件,那么这些c++20的相关的头文件要从哪里来呢?这些是我以前没有想过的,现在想来是很复杂的问题,这似乎都是clang如何部署的问题,我现在能够看到的是llvm自带的concepts文件是libcxx目录下的llvm自己的portable的版本,这似乎预示的是只有使用llvm自己的libcpp才行。所以,是否说明编译llvm/clang的gcc自身必须要支持c++20才能让clang支持c++20呢?
  3. 这个选项-DLLVM_PARALLEL_LINK_JOBS 原来只是ninja才有用!
  4. 我最后找到了这个命令选项--gcc-toolchain=/home/nick/opt/gcc-11.2.0,对应于cppinsights就是把这个加在最后的选项里。花了差不多两天才搞明白这个东西,这个的确实一个非常复杂的问题,就是说你编译的clang是不能指望它在安装的时候负责拷贝或者自带有关于c++20之类的libstdc++的相关头文件的,因为这个不是它的义务或者权力,这个纯粹是编译clang的工具要做的,除非说你要使用纯粹clang自带的libcpp和它的runtime之类。这个是一个概念问题,是无解的。

三月二十一日 等待变化等待机会

  1. 发现了一个cppinsights的很小的问题,就是alignas(128)是否是变量声明的一部分,你从语义的角度当然可以看作specifier是变量的有机组成,可是如果我从编译器的角度来看我宁可它是一个assert或者是一个operator的表达式,也就是说它并不是类型的一部分,这里说的它就好似使用static_assert来实现的一眼。比如alignas(128) char ary[128];里作为的出来变量的部分实际上是char ary[128];并不包含alignas(128)的部分的。所以, cppinsights在替换变量的时候就重复了一遍aliagnas(1208)

三月二十二日 等待变化等待机会

  1. 关于Makefile的变量我总是记不住
    automatic variabledescriptionremarks
    $@The file name of the target of the rule.冒号的左边,就是目标文件
    $%The target member name, when the target is an archive member.这个太高级了是foolib(hack.o)里的hack.o我根本用不到
    $<The name of the first prerequisite.对于我来说冒号右边只有一个,所以,都一样
    $?The names of all the prerequisites that are newer than the target, with spaces between them.有多个前提的情况是怎么样子的呢?
    $^The names of all the prerequisites, with spaces between them.全部的依赖前提
    $+This is like ‘$^’, but prerequisites listed more than once are duplicated in the order they were listed in the makefile.前提里重复的部分?
    $|The names of all the order-only prerequisites, with spaces between them.order-only是什么意思?不懂!
    $*The stem with which an implicit rule matches这个是最有用的,就是Match的框架部分,我的感觉就是wildcard部分,比如dir/%.cpp:然后就是文件名没有后缀,这个似乎和官方文档不符或者我理解有问题
    $(@D)The directory part of the file name of the target, with the trailing slash removed.这个很有用,好像dirname一样
    $(@F)The file-within-directory part of the file name of the target.如同basename一样
    其他高级的功能我也一时掌握不了。
  2. 这中间我写了一个简单的规则来检验所有的expect文件和我程序运行结果,这里使用cmp要比diff来的好,因为后者还是要多一些信息,反而不好看它的运行结果: 这里只要保证我的report文件是新创建的就好了。
  3. 我的实验部分成功了,因为之前我对于makefile的依赖有一个不知道哪里来的莫名其妙的观念,就是没有先决条件的就应该无条件执行,这个简直是不可理喻,空的目标当然要无条件执行,可是当你的目标是一个真实存在的文件你要怎么无条件执行呢?当然是找一个最新的依赖文件让它无条件执行了!这么简单的道理我却转不开,真的是白学了这么多年。然后我就发现了一个大侠测试的例子,这个是明显编译不过的,但是我却领会不了其中的含义,直到google这个错误Why lambda expression's capture list cannot be decomposed using structured bindings,这个对于我是震聋发聩的问句,我从来也没有想过这个问题,那么根本就不知道答案了!这位大侠的神奇操作让我惊叹不已,这里的overload就是传说中的神器,我似乎在cppcon上挺过祖师爷介绍过很遗憾没有被委员会接纳,但是这个神器我应该好好使用! 这是在做什么呢?就是说我有一个万能的神器可以包含宇宙万物,可以像孙悟空72变一样随影如行任意变化它看到的类型,而且又如乾坤一气袋一样可以包含不管多少的类,并且我还要能够继承它们的成员函数,也就是把看到的functor的功能都保留下来。神似型不似?当然这个是第一行代码,第二行代码是deduction guide之类的,是对于constructor的指示。这个是绝好的例子,需要学习。 当然大侠这里是利用了GCC的漏洞来把lambda的内部秘密公开化了,而clang做的很好,一眼就看穿了其中的伎俩直接拒绝了。不得不佩服clang!
  4. 关于脚本的基本常识我还是要理解:我的理解就是循环部分不会立刻退出,条件判断不会退出,逻辑条件不会退出除非是最后一个,流水线也不会除非是最后一个,所以,我可以把有可能出错的语句假装成逻辑判断
    The shell does not exit if the command that fails is part of the command list immediately following a while or until keyword, part of the test in an if statement, part of any command executed in a && or || list except the command following the final && or ||, any command in a pipeline but the last, or if the command’s return status is being inverted with !. A trap on ERR, if set, is executed before the shell exits.
    这里有高级的错误处理,不过我还没有到那个层面。这里的问题是使用错误信息如果我的系统的默认语言不是英语就不行了,如果使用错误代码就好了,可是这个在脚本里几乎不可能的。
  5. 真的是脚本人人会写各有巧妙不同啊,比如如何把当前的错误信息存在一个变量里呢?使用read,这个我在manpage里还找不到,就是说你在pipeline里读一直到delimiter是"\0"再停止,因为默认是换行符,
  6. 而这里是上千点赞的经典问题和经典回答:我一直没有意识到command 2>&1 >/dev/null | grep 'something'虽然stderr被导引到stdout,但是随后只有stdout被导引到了/dev/null,我以前一直以为两个是混合同流合污再被导引的。这个就是关键。
    Note that the sequence of I/O redirections is interpreted left-to-right, but pipes are set up before the I/O redirections are interpreted. File descriptors such as 1 and 2 are references to open file descriptions. The operation 2>&1 makes file descriptor 2 aka stderr refer to the same open file description as file descriptor 1 aka stdout is currently referring to (see dup2() and open()). The operation >/dev/null then changes file descriptor 1 so that it refers to an open file description for /dev/null, but that doesn't change the fact that file descriptor 2 refers to the open file description which file descriptor 1 was originally pointing to — namely, the pipe.
    如果不看这个解释我是永远不明白其中的奥妙的,这个完全是脚本执行左右顺序。这个就是关键。所以,错误信息存在变量里可以简单的result=$( ./foo 2>&1>/dev/null )这里的./foo是我的脚本文件名。

三月二十四日 等待变化等待机会

  1. cppinsights提了一个bug。又提了一个建议增加例子说明。这个是一个编译的小问题,都是在最新版gcc的编译问题。
  2. 首先,经过之前的很长时间我大体上是熟悉了GCC的编译,称不上精通,但是基本经验教训是有了,现在需要同样的反复实践clang的编译安装运行,目前来看最好的编译方法不是使用Makefile而是使用ninja,这个是需要接受的一个事实。但是clang最大的障碍就是编译的时候耗费的磁盘内存太大了,我只能每次都把之前编译的结果删除。这方面GCC确实是有无可替代的优势。同时,我在ubuntu上编译clang就遇到一个小问题,如果使用系统的gcc-7.5编译clang13/4那么c++20的新特性就只能指望clang13/14自带的libcpp了,而要使用原生的GCC自带的libstdc++最好是使用我自己编译的GCC-10/11来编译clang,那么这个时候是否要使用c++标准20来编译呢?答案又是否定的,似乎目前clang的代码本身还是仅仅支持c++17,这里并不是clang编译器不支持c++20而是其代码本身还是在c++17,所以,这个是我的命令配置:
    ~/Downloads/cmake-3.18.4/bin/cmake -G "Ninja" -DLLVM_USE_LINKER=gold -DLLVM_USE_SPLIT_DWARF=ON -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra" -DCMAKE_BUILD_TYPE="RELWITHDEBINFO" -DLLVM_TARGETS_TO_BUILD="X86" -DLLVM_BUILD_LLVM_DYLIB=ON -DLLVM_STATIC_LINK_CXX_STDLIB=ON -DLLVM_PARALLEL_LINK_JOBS=1 -DLLVM_PARALLEL_COMPILE_JOBS=4 -DLLVM_ENABLE_IDE=OFF -DLLVM_ABI_BREAKING_CHECKS="FORCE_OFF" -DLLVM_INCLUDE_TOOLS=ON -DLLVM_BUILD_TOOLS=ON -DLLVM_INCLUDE_UTILS=ON -DLLVM_BUILD_UTILS=ON -DLLVM_BUILD_RUNTIMES=OFF -DLLVM_BUILD_EXAMPLES=OFF -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_BUILD_TESTS=OF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_INCLUDE_GO_TESTS=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_BUILD_BENCHMARKS=OFF -DLLVM_INCLUDE_DOCS=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_BINDINGS=OFF  ../llvm-project/llvm

三月二十五日 等待变化等待机会

  1. clang的开发相比gcc感觉有些混乱,就是说不够稳定,一个是它需要兼容的东西多,一个是它的包袱少。各有利弊吧。总之我使用gcc-11.2.0编译clang13有错误,只有clang14可以编译成功,但是我发现安装的动态库有些混乱为什么libclang.so指向的是clang13,这个是soname设定的问题,总之symlink也是混乱的,不知道是我的编译环境的问题还是代码配置的问题,总之,我打算再用gcc-7.5编译一次看看。
  2. 链接clang/llvm的内存消耗是巨大的,只有这个选项才能解决-DLLVM_USE_SPLIT_DWARF=ON -DLLVM_USE_LINKER=gold

三月二十六日 等待变化等待机会

  1. libclang.so的soname为什么不更新到最新的14呢?我忍不住提了一个bug。也许是我理解错误了,可是这么做非常的让用户混乱以为自己编译链接有问题啊!因为soname和realname不同的特殊设计除非有什么很好的理由是站不住脚的。
  2. 目前看来cppinsights之所以没有更新到llvm14也许是对的,因为至少目前bug挺多的,最基本的这个动态库的soname就不对让人很烦心,害得我反复编译了两三次浪费了一天多的编译时间,要知道编译一次llvm就要几个小时,遇到中间内存不足出错重新配置再次编译又是从头开始,还遇到编译完了把磁盘空间也用完了情况又要重来,结果每次编译我都要至少预留至少50G的磁盘并且把eclipse关闭因为链接的时候十几个G的内存都不一定够,直到我找到了这个选项-DLLVM_USE_SPLIT_DWARF=ON LLVM/clang虽然好可是真的要让你使用也是不容易啊!
  3. 通过这几天的编译clang我更加深刻的认识了clang的原理,就是编译llvm/clang并不要用更高版本的GCC因为这些测试反而少问题更加多,比如ubuntu系统默认的gcc-7.5就是最佳的编译选项,如果不希望使用clang自带的runtime或者是libcpp的话,那么就只能在使用clang的时候添加选项--gcc-toolchain来指示高版本gcc的根目录。但是这个是我目前比较担心的,因为编译clang的GCC和运行clang的GCC版本不一致如何能够保证兼容性呢?这个肯定是很粗糙的简单做法,我想如果要完整解决可能还是要使用同一个版本或者使用clang自己的libcpp以及runtime。这个问题太复杂了,我要实验一下。
  4. 我的实验打算分两步:首先是实验使用最新版本GCC-11.2.0来编译llvm-13,看样子要使用c++17来编译看看。第二步是使用gcc-7.5来编译llvm-13使用libcpp以及runtime,这个是完全的clang的自带也许可靠性更高一些吧?
  5. 另一个有趣的事情是我一直没有意识到的,就是在我安装了llvm之后我可以单独下载clang的源代码直接编译,这个也是一个奇妙的组合,llvm作为一个有一定稳定性和兼容性的开发工具来编译任意版本的clang来单独运行?
  6. 就是说从这里下载相应的代码我也可能写一个通用的脚本来任意编译某个版本的llvm的项目或者工具。
  7. 这个真的是一个复杂的谜题!这里的核心是decltype(auto)是什么!
    type-constraint(optional) decltype(auto)

    type is decltype(expr), where expr is the initializer.

    这个要怎么理解呢?这个是gcc-11的修改才导致这个严格的限制。而这个依据是标准的[dcl.type.auto.deduct]/4这里,搞到最后才明白了一点,就是说decltype(auto)是一个语法上要求不能有任何修饰或者说cv-qualifier,但是语义上是允许的,这一点GCC程序员和标准委员会沟通过已经证实了,所以,前面除了constexpr是可以其他cv-qualifier不行,这个应该是gcc-11.xx的一个regression,在gcc-12修正的。
  8. 我惊讶的发现如果使用-DCMAKE_BUILD_TYPE="MinSizeRel"结果编译出的文件大小差别小了十倍以上!看来我之前被困扰是因为debuginfo的部分占文件的大部分了?
    ~/Downloads/cmake-3.18.4/bin/cmake -G "Ninja" -DLLVM_USE_LINKER=gold -DLLVM_USE_SPLIT_DWARF=ON -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra" -DCMAKE_BUILD_TYPE="MinSizeRel" -DLLVM_TARGETS_TO_BUILD="X86" -DLLVM_PARALLEL_COMPILE_JOBS=8 -DLLVM_PARALLEL_LINK_JOBS=1 -DLLVM_BUILD_LLVM_DYLIB=ON -DLLVM_LINK_LLVM_DYLIB=ON -DLLVM_STATIC_LINK_CXX_STDLIB=ON -DLLVM_ENABLE_IDE=OFF -DLLVM_ABI_BREAKING_CHECKS="FORCE_OFF" -DLLVM_INCLUDE_TOOLS=ON -DLLVM_BUILD_TOOLS=ON -DLLVM_INCLUDE_UTILS=ON -DLLVM_BUILD_UTILS=ON -DLLVM_BUILD_RUNTIMES=OFF -DLLVM_BUILD_EXAMPLES=OFF -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_BUILD_TESTS=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_INCLUDE_GO_TESTS=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_BUILD_BENCHMARKS=OFF -DLLVM_INCLUDE_DOCS=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_BINDINGS=OFF ../llvm-project/llvm

三月二十九日 等待变化等待机会

  1. 这个是一个试金石,我以为我会写SFINAE了,结果还是写不出来。因为我想找一个能够检验一个类是否有某一个成员函数的方法,结果写不出来只好求助stackoverflow的各路大神们了。这里就是从各种纷繁的解决方案挑选一个合适的,当然如果是支持c++20的话,写起来是没有难度的,因为人人都知道可以用万能的requires,难的是在这之前的各种神奇的写法,这个需要我好好品味学习。这个是一个做法 就是说一个类有一个方法ToString返回string类型。这个SFINAE的做法要好好学习。当然啊,这个是比较的有局限性的因为没有使用declval而是用到了T的constructor是有问题的。

三月三十一日 等待变化等待机会

  1. 偶然翻到祖师爷写的经典著作《The C++ Programming Languange》,所谓圣贤著作要常温常读才对,何况我一次都没有读完过。
  2. 偶然看到这个例子就是很棒,原本unique_copy只是保证去除相邻的相同的字符,那么通常情况下如果使用默认办法去除一个词组间多余的空格是有副作用因为会去除词组里相邻重复的字母,那么灵活运用就可以只去除空格了,这个想法是很不错的,很简单我却没有想到。这个是看祖师爷著作的收获。

四月一日 等待变化等待机会

  1. 我也想不出有什么更好的办法,但是这些不都是常识吗?
  2. 枉自学了这么多年的模板,那么模板函数的instantiation的前提是什么?当然是要首先有模板的定义了,这个也许是和普通自由函数可以使用extern来让linker去在链接时候再去解决的最重要区别吧? 这些道理我认为我是懂的,可是实际应用起来就又感觉不得要领
    A function template by itself is not a type, or a function, or any other entity. No code is generated from a source file that contains only template definitions. In order for any code to appear, a template must be instantiated: the template arguments must be determined so that the compiler can generate an actual function (or class, from a class template).
    那么很简单的问题就是在instantiation使用extern的情况下要不要包含定义呢?当然要了,简单的说就是在instantiation的时候definition必须是要的,核心的原因就是模板本身不是一个类型,所以,当你使用extern template instantiation的时候编译器没有能力去解决一个并非类型的模板定义,因为离开了模板参数的模板定义不是一个类型,是不能单独存活在编译器的上下文的搜索范围里的。更简单的说模板是类型的一个部分,离开模板参数的模板定义代码是无意义的代码。所以,回到古老的话提就是能否模板库的开发者能够隐藏模板的定义呢?这个理论上是不可能的,因为模板应用的核心场景就是用户自定义模板参数,但是在用户给出自定义参数之前编译二进制模板库而没有用户的模板参数是不可能的。这一点应该是在头文件里是铁律,不知道module的引入是否改变了这个规则。

四月二日 等待变化等待机会

  1. LLVM发来的bug状态,我压根就看不懂我当时是怎么提的bug,可能也是忘了,花了好久才大概理解就是clang目前承认这个还没有实现
  2. 而要理解标准遇到这个经典的问题什么是immediate context?,这个经典的回答是非常的翔实深入的,可是单单理解回答就是一个不小的努力。这个部分实在是太艰深奥妙了,应该是属于最顶级的语言专家和编译器实现者以及集两者于一身的标准制定者才能够深刻认识的范畴!我想尽量用自己的话来总结一下都很困难,还是摘抄一部分吧:
    If you consider all the templates and implicitly-defined functions that are needed to determine the result of the template argument substitution, and imagine they are generated first, before substitution starts, then any errors occurring in that first step are not in the immediate context, and result in hard errors.
    这个定义说明了什么呢?就是作者说的在准备阶段就出错了这个是错误,是没有理由不失败的,换言之,不可能发生SFINAE的那样静悄悄的fallback。因为这个不是简单的模板参数匹配的失败,而是完全的不可能。
    If all those instantiations and implicitly-definitions (which might include defining functions as deleted) can be done without error, then any further "errors" that occur during substitution (i.e. while referring to the instantiated templates and implicitly-defined functions in the function template's signature) are not errors, but result in deduction failures.
    这里说的就是deduction failure相对来说是比较性的错误,是可以容忍的。这里所谓的further error让我想起了经典的一些例子,我模模糊糊记得哪里看到的scoped context的相关的错误可能是这个情况吧,当然作者的例子说的就是这个意思,并不是我想到的,而是作者的例子让我明白的。作者使用了非常翔实的例子和详细的解说,非常的有说服力,就是我在上文标准里始终不明白的地方,这里画龙点睛的意思
    The intent is to avoid requiring implementations to deal with substitution failure involving arbitrary statements.
    如果我能体会到就是真正理解了什么是immediate context的真谛了。我的理解就是说如果第一步就不扎实的话就不要再迈步向前了,因为一个常识是一个小小的错误可以进一步无限放大为任意复杂度的错误,就好像射线一样的无限放大作用,这样子减少编译器实现的难度,这个当然是朴素而简单的理解了。但是核心还是要理解所谓的immediate context的定义是什么:
    The immediate context is basically what you see in the template declaration itself. Everything outside of that is a hard error.
    这位作者没有任何的解释有涉嫌拷贝粘贴的意味,但是如果作者认为这个定义是所谓的天经地义,不言自明的话,这个解释也算是言简意赅,因为这么简单的问题需要标准专门给出定义来解释吗?你需要用英语解释英语吗?我搜索标准得到了这个可能的定义:
    An expression or conversion is in an immediate function context if it is potentially evaluated and either:
    • its innermost enclosing non-block scope is a function parameter scope of an immediate function, or
    • its enclosing statement is enclosed ([stmt.pre]) by the compound-statement of a consteval if statement ([stmt.if]).
    An expression or conversion is an immediate invocation if it is a potentially-evaluated explicit or implicit invocation of an immediate function and is not in an immediate function context. An immediate invocation shall be a constant expression.
    这个是否是immediate context的定义呢?我读起来都费劲,不明白。好像不太相关。我只好求助于google的site search使用这个site:https://timsong-cpp.github.io/cppwp immediate context来搜索,看来这个并不是一个严格定义的语法词组,而是普通的英语含义,但是理解上是比较有困难的,尤其是对于非英语母语的人来说更是如此。
  3. 看来今天早上脑子短路了因为又对于lambda的类型开始犯迷糊,比如这样子明显无意义的代码
    
    void foo(decltype([](){}));
    
    看出来问题了吗?这个是一个无用的代码在于decltype([](){})永远没有类型可以匹配因为这个是永远成立的: static_assert(!__is_same(decltype([](){}), decltype([](){}))); 你能够找到一个类型是decltype([](){})的实例吗?找不到!因为每一个lambda的实例都是unique的,
  4. 早上看到了这个void foo(...){}我又开始犯迷糊了,这个是代表任意多个参数,那么它就覆盖了所有可能的参数形式,那么它的意义在哪里呢?是否说所有的overload都可以被覆盖了,如果它和模板函数结合的话是什么意思?就是说如果SFINAE失败总是有fallback,对吗?

四月三日 等待变化等待机会

  1. 无意中看到这个关于decltype(auto)的说明才明白原来它的设计是为了perfect forwarding的!首先,还是先明确一下它的,我的理解就是decltype(auto)是由它的所谓的initializer的类型来决定的。
    
    int n=0;
    int& intRef=n;
    auto intCopy=intRef; // auto by default declare a copy of int
    static_assert(__is_same(decltype(intCopy), int));
    decltype(auto) intRefRef=intRef; // decltype(auto) using its initializer type which is int reference
    static_assert(__is_same(decltype(intRefRef), int&));
    
    也就是说如果decltype(auto)保留了它的initializer的类型而不是由编译器自己去推断或者任意定义的。这里的例子其实很深奥,我还是不能完全体会所谓的perfect forwarding的真正应用: 我的理解就是要保留原来的函数返回类型,因为很多时候函数返回类型是由编译器来决定的,如果使用auto的话,或者被优化了,比如所谓的RVO或者官方名称Copy Elision
  2. 然后我就遇到了我今天的难题,这个lambda到底是什么类型?所以,这里不要以为lambda是声明了一个默认参数类型为一个lambda的lambda类型,而是模板
    
    void foo(auto lambda=[](){}){}
    
    24.04.2022只能说这里的=[](){}是无用的费代码,编译器不会报错,但是也不会接受,让我看看标准是怎么说的。或者说我设想的一个函数的参数是一个有默认值的lambda的情况是不可能的,因为lambda是不可能被当作参数传递的,因为它的类型是唯一的,这个也是c++类型严格检查以及设计lambda的初衷! 耗费了我半个早上我才理解原来这个是c++20的新feature,就是说这里的auto实际上等同于模板参数声明,所以,它的类型是不确定的,因为它仅仅是一个placeholder或者说是模板类型变量之类的,在模板使用之前是不可能确定的。所以,它们是等效的 这个在模板实例化之前当然编译器不会报怨两个模糊的重载,但是因为它们是等效的,所以,当你只给一个参数的时候编译器不知道哪个模板才是正确的。另外,这里就看出了定义一个万能的参数函数void foo(...){}可以依靠参数个数来识别它代表了所有参数超过一个以上的重载
  3. 这里是更加完整的关于decltype(auto)的帖子,我也没有耐心看下去了,太精深奥妙了。

四月十七日 等待变化等待机会

  1. 生活被打断了,慢慢的平静下来,翻看以前的日记,这个是阅读以前的日记的心得。

四月十八日 等待变化等待机会

  1. 至为浅显的道理也要温习。
  2. 作为练习这个是产生一个tuple的小函数,我在想这个是不是我当初要做的呢?时过境迁,过去的思想已经逝去,只能凭借只言片语来揣想,其实我所要的也许就是这么简单的几行代码,这个有那么难吗? 说到底就是对于parameter pack的真正的掌握,以及所谓的简洁lambda的写法。
  3. 之前我对于关键字noexcept几乎完全没有概念,现在要认真体会:
    1. 它是管键字
    2. 它是一个操作符,返回值为bool
    3. 它能够在编译期计算一个表达式是否有可能抛出异常
    4. 它太强大了!

四月二十三日 等待变化等待机会

  1. 对于wifi的信号强度这种简单的问题我都没有概念,总以为好像信噪比之类的是越小越好,但是强度是一个功率单位吧,虽然是负数应该是越大才越好,不是吗?这个常识我都没有。让我感到气愤的是我买无线路由器的时候为什么不看看信号强度的指标!-50 dBm以下才是好东西!
  2. 而我对于数组的类型总是模糊的概念:为什么老是犯这种低级错误呢?对于[[maybe_unused]]int a1[][3]={{1,2,3}};里的a1[0]的类型是什么? 这个不是明摆的事实吗?初始化让编译器推理得到它的第一个dimension是1,这个毫无疑问,但是关键是a1[0]得到的类型是引用!这个我始终没有意识到。这个是很重要的问题,为什么?因为它直接导致我对于我不熟悉的extent产生了怀疑,比如 这个是为什么?首先撇开a1[1]这种无稽之谈,因为数组类型编译器不会检查边界的。那么为什么它的extent居然是0呢?答案只有在明白了引用才能明白! 希望别人不要犯同样的错误,我添加了一个小小的例子。 忙活了一两个小时就学到一个这么小的东西。感觉每天都是从零开始一样的。
  3. 同样的例子也适用于rank,所以,我也添加了我的例子。

四月二十四日 等待变化等待机会

  1. 曾经记得lambda作为一个独一无二的类型对于函数参数这种类型固定来说似乎是无法定义的,比如我只想说我的函数参数接受一个可以呼叫的类型,其实并不在乎具体的类型甚至于它需要的回调函数的参数类型我都不在乎,也就是一个万能的呼叫函数,那么这个是典型的一个模板,而这里的参数与其说是类型不如说是概念(concept),可以写成严格的模板方式: 比如你可以这样子调用 这么做有的时候感觉太严格了,万一不能调用或者有多余的参数,当然我给不出理由为什么不行,只不过还有另一种做法就是简化的模板做法,写的很随意的用auto来做的万能的模板 这个当然有些无厘头了,说好的回调函数你碰到一个不懂行的用户不知道要怎么传参数,传错了,你决绝的拒绝是发生在编译期,你选择绕过他反正也不一定是什么大不了的,放过吧。于是 前面三个调用是合法的,最后一个你选择轻轻放过,我也不知道这么做有什么好处都是编译期可以纠正的为什么不指出来呢?

四月二十八日 等待变化等待机会

  1. ssh-copy-id肯定是有玄机的,也许是pam的命令设置,总之手动拷贝有时候是会失败。

五月十日 等待变化等待机会

  1. 工作中反复听到jumper,但是始终不明所以然,其实就是ssh的一个类似中继一样的跳跃也许是防火墙后面也许是。。。我现在遇到的是使用vagrant/ansible创建的virtualbox的虚拟机,它的登陆使用了本地ip和port来通过ssh登陆,这个可以直接使用
    
    ssh -J jumperUserId@JumperIp  -p localPort vbUser@localhost
    
    另一种做法是在我的.ssh建立一个config文件,然后定义
    
    Host	Jumper-alias
    	HostName	JumperIp
    	User	JumperUserId
    	Port	JumperPort  # by default 22 for ssh
    Host	VB-alias
    	HostName	VB-Ip  # 127.0.0.1
    	User	VB-userId
    	Port	VB-port
    	ProxyCommand	ssh -W %h:%p Jumper-alias
    
    为了免除输入密码的繁琐可以使用ssh-copy-id创建ssh-pubkey,至于vagrant/ansible它使用了自己的pubkey创建的这个虚拟机的~/.ssh下有一个命名为authorized_key的公钥,这个是可以通过查询虚拟机的/etc/ssh/sshd_config里的配置明白的:AuthorizedKeyFile ~/.ssh/authorized_key让我意外的是这里#PubKeyAuthentication Yes居然依旧可以使用pubkey,也许没有yes也是默认的?

五月二十日 等待变化等待机会

  1. 对于vagrant来说,使用一个环境变量BRANCH可以配合ansible脚本使用git的branch,不过最主要的idea还是在于gitlab需要我的pubkey,那么我创建的rsa_id的privatekey要拷贝到虚拟机里去,使用ansible的"provision" "file"命令可以拷贝。
  2. 我第一次意识到vscode很可能是最好的IDE,这方面似乎eclipse还差的很多,尤其是那个remote-explorer的配置,在.vscode下面配置一个include path,这个我今天也决定把这个备份拷贝的工作放在了ansible的shell脚本里。
  3. 而ansible里的privatekey文件被我替代了我自己的rsa_id这样子创建的虚拟机就可以自动创建.ssh/authorized_key,而这个是sshd_config的设置要求。
  4. 当我创建vagrant虚拟机的时候遇到什么socket的错误的时候其实是/tmp/.vbox-xx的目录的访问权限的问题,大概是IPC的unix-socket之类的吧,把它删除就是了。
  5. nohup的环境变量是在运行之前设置var=val nohup xxx这个我始终不成功,最后准备尝试nohup sh -c 'export var=val && vagrant up' & 就是使用单引号。

五月二十三日 等待变化等待机会

  1. 改编自《洛神赋》

    翩若惊鸿婉游龙, 荣曜秋菊茂春松。 髣髴轻若云蔽月, 飘飖流如雪回风。 远望朝霞太阳升, 迫察渌波芙蕖出。 肩若削成秾纤得, 腰如约素修短合。 芳泽无加铅华御, 延颈秀项皓质呈。 云髻峨峨修眉联, 丹唇朗朗皓齿鲜。 明眸善睐靥承权, 瓌姿艳逸静体闲。 柔情绰态媚于语, 奇服旷世骨应图。 瑶碧华琚披罗衣, 明珠耀躯戴金翠。 雾绡轻裾践文履, 踟蹰山隅微芳蔼。 忽焉纵体遨以嬉, 左倚采旄荫桂旗。 皓腕兮兮攘神浒, 湍濑乎乎采玄芝。
  2. 前日,也就是世俗小儿女在兹念兹的5.20,其时正当二十四节气之小满,家父有诗为训:

    《小满述怀》

    小满缘何没大满, 满招损来益是谦, 虚怀若竹谦如谷, 牢记古训敬先贤。
    的确,节气里有大寒小寒,大暑小暑,大雪小雪,然而有小满而无大满,这是为何?我以为天之道损有余而补不足,是故常人连小满都求而不得更逞论大满乎?连古代大贤大哲都是天下不如意之事十常八九,何况这些凡夫俗子?所以,想通了这一层也就豁然开朗,知足常乐就是小满,平常人到了这一步就只有小满,根本没有啥大满的可能了,也许这就是先帝不设大满的原因吧?

    所以 有诗为证:

    寒暑大小各结伴,
    大雪小雪紧相连。
    缘何小满无大满?
    黄帝摇头犯了难。
    
    世间万物都有缘,
    哪能人人皆遂愿?
    倘若凡事分大小,
    人心不足没有边!
    
    游戏文字博诸君一笑而已。
  3. 感觉ssh的长任务总是断线干脆把/etc/ssh/sshd_config里的client链接时间改长一些。修改之后其实不需要重新启动服务因为systemctl reload sshd我猜想是仅仅重新读取配置文件,

六月六日 等待变化等待机会

  1. 今天的教训是在最最普通的vector的emplace_back/push_back翻船了,其实,从支持move的角度来看两者都不是关键,如果不能够使用ctor的参数来创建元素的话,两者都能够同样的完成,甚至这一点上和assignment operator也是一样,因为它们都重载了rvalue-reference的参数形式。

六月十二日 等待变化等待机会

    早晨起来有感而发。
  1. 六月十二日是俄罗斯联邦国庆日,看到新闻采访俄驻华大使谈当前国际风云变幻引用唐诗“山雨欲来风满楼”有感。我并非完全反对或支持任何一方的立场,但是对于战火点燃的过程中,国际舞台上政客的儿戏态度感到愤慨。
    世如累卵惹人愁,
    俄乌战火何时休?
    社稷生死成儿戏,
    干将辅国当用谋。
    
  2. 加州这里汽油近日都快突破7美元一加仑了,超市里很多商品价格也上涨了不少。各个国家人民在经历了大流行之后都是疲惫不堪,然而更大的危机似乎是“山雨欲来风满楼”还在酝酿之中。而左右几十亿人类命运的棋手就是那几个大国的几十个人,他们的举棋落子决定了历史的走向,他们是在用决策撰写春秋历史。
    俄乌战火烤美欧,
    缺油少粮撼五洲。
    窗外晨鸟吱吱叫,
    大国棋手写春秋。
    
  3. 俄乌矛盾有年头,
    美欧挑唆忙添油。
    石头搬起砸到脚,
    看你会不会发愁?
    
  4. 俄乌战火有缘由,
    矛盾由来朔源头。
    本来吃的一锅饭,
    所谓冲突为何求?
    
    当年分家无远谋,
    以为单过有盼头。
    打断胳膊连着筋,
    荆棘满途永不休。
    
    推波助澜有寡头,
    一为军火二为油。
    欧美祸心搞对立,
    可怜兄弟打破头。
    
    千万难民生计愁,
    西方暗喜看耍猴。
    社稷岂能轻托付?
    戏子小丑有何谋!!
    

七月一日 等待变化等待机会

  1. 工作中一个同事把一系列的检查函数放进若干个vector来顺序调用来检查一组参数的正确性,这里遇到一个问题就是这些检查函数需要的参数个数不一,那么作为vector你只能把它们定义成一样的参数个数的回调函数才可以,那么这个似乎有一点点的受限制,所以,我花了一个晚上殚精竭虑的改进了一点点
  2. 首先,为了简化问题我们假定参数最多就是三个: 而我们的加插函数不妨也抽象成三个类型,当然可以增加更多
  3. 首先,我们先实现一个根据函数所带参数个数来自动匹配的帮助函数 这里我想是可以写成更加一般化的,只不过我的参数个数是固定的我就懒得写更加复杂的做法,因为很快发现我的主要问题在后面的tuple里
  4. 其实,我是低估了问题的复杂性,因为我把函数指针放到tuple里的时候我糊涂了搞混了tuple里的函数类型和函数自己的参数类型。这里还有一个小插曲就是使用c++20来写的话更加的简洁,因为可以使用lambda: 注意这里的fold expression困扰了我一个晚上!
  5. 如果使用c++17的话就麻烦一点 我感觉这个还是有一点点的难度的,因为好久没有写这个元编程很多就忘记了。比如递归里使用tuple如果无法使用constexpr的判断,如果超过tuple范围就出错的问题才让我回过头来使用fold expression,它是可以达到第一个返回的。

七月二日 等待变化等待机会

  1. 很多时候你以为只有一步之遥,似乎没有什么障碍的可以轻松跨过,没想到却费了这么多的心力才完成,看来知易行难是多么的深刻!昨天晚上我曾经说让一个函数指针自动匹配符合它参数个数的参数来调用是一个很简单的步骤,看来打脸了,因为有一些小细节。而这其中给我最大收益的是解决如何把一个非constexpr的函数转变为constexpr的技巧
  2. 首先,我们需要一个能够计算函数指针它的参数个数的函数,这个很容易,但是把它变成constexpr的就不那么容易了!这里的技巧要深刻领会! 看懂了吗?这里的关键是要把返回值做成类型,而这个关键原因是我必须把函数指针作为实参代入函数结果导致我使用的是实参而不是模板函数的型参,所以,普通的直接返回一个数字的做法是不行的,因为我调用的时候必须传递一个参数导致整个函数不能成为constexpr。而函数返回值是一个类型的好处是我根本不用真的调用函数而是使用decltype来获得函数的返回值类型就可以,这样子就实现了函数的constexpr化!这里的精微奥妙是不可言喻的。
  3. 那么接下来真的就是只有一步之遥的简单的 注意这里我因为使用了模板lambda在clang里严格区分了c++17和c++20的特性,而在gcc里很多的功能提前就实现了,即便没有打开开关它也在那里。但是为了兼容性还是要写一个c++17的严格的版本。
  4. 这个就是c++17的版本 这里注意这个所谓的static_assert来判断函数指针是否真的匹配参数类型也是大家常用的手段,这个在缺乏concept的尤其普遍,而值得注意的细节是派生的新类型需要添加额外的typename关键字。
  5. 而检验实际上和昨天的几乎一样,就是把函数指针传递进去连同参数tuple看看是否正确获得调用。
  6. 《三体》里的人物名实际上透露了作者对于他们的性格命运以及在小说里角色功能的意义,这个是一个见仁见智的领域,而且作者不说明大家只能各自揣摩。当然,毕竟是一部非文学作品没有达到《红楼梦》那么设计的细致,不过本着研究的态度我有感而发给一些人物做了一定场诗来尝试渲染人物的命运性格,也许可以作为人物出场的时候的背景介绍吧。
  7. 首先是叶文洁,她的名字应该是“夜,问诘”。想想看,她在雷达峰值夜班的一个个夜晚,面对着无边无际的宇宙噪音回波一次次的发出来自内心深处的呐喊与责问,为什么?为什么?为什么?是否真的有主宰?是否真的有正义?是否冥冥之中有超过自然的伟大力量来改变命运?而当她在生命的最后时刻得知她所期待的能够消除人类暴政的三体文明是如此的凶残冷血的时候,她为自己打开潘多拉魔盒放出来的毁灭人类的恶魔感到了无限的怅悔。
    天道渺茫夜无边,
    寰宇怨气问苍天。
    三体无心伐桀纣,
    怨女有悔乱人间。
    
    这首诗里嵌入了主人公的名字。
  8. 《三体》中据说最被日本读者喜欢的人物居然是史强,看似出乎意外其实却在情理之中,因为大和民族是一个崇拜强者的名族,史强是一个贯穿几个世纪的英雄人物,他的名字大概没有人会质疑:那就是英文里的strong,强者无敌!他是一个性格最鲜明也是我认为塑造最成功的人物形象。他难道不值得歌颂吗?
    杀伐决断敌胆寒,
    忠义无边美名传。
    生逢乱世何所惧?
    舍生向死凯歌还。
    
  9. 三体中有一个如流星般一晃而过的悲剧人物,作者着墨不多,但是却让人倍感同情的女性,她就是申玉菲。很明显的她的名字是谐音“伸展翅膀欲飞冲天”。她对于她的主有着宗教般的虔诚,而对于自己的同类却是冷若冰霜,然而在这层冷漠之下还是充满了爱,最后为了阻止人类的灭亡命运而被更加邪恶的潘寒杀害。她的悲剧就在于她不能被任何一个阵营所宽容的悲剧命运。
    身世迷离冷雪霜,
    一心救主想断肠。
    欲飞广寒借仙药,
    陨落星辰梦不长。
    
  10. 主人公汪淼是让人难以捉摸的,他的形象是多元的,我似乎很难透过他的名字来揣测作者当初设计的意义,但是他代表波涛浩渺的大海是无疑的,这个包括他登陆三体游戏的帐号“海人”也可以得到应证。那么在中国古代哲学中,有容乃大的大海,以及至柔莫过于水的至柔克至刚的哲学思想也许可以解释他的名字。另一方面,在老子看来海之所以成为大海是因为它主动取低势这样子才能接纳广大的河流形成博大精深的汪洋。这一点契合了汪淼的纳米科技特点,因为在一众科学边界的大科学家面前,纳米科技是低了一个层级的应用科学,是一个比前沿退后很多的“工程技术”,算不得被三体需要锁死的科学。当然三体实际上是知道纳米技术的重要性,因为它是太空电梯的实现成为可能。而正是因为汪淼自认为自己没有那么高的理论物理与哲学的领悟高度反而逃过一劫,因为理论物理进入了空灵的哲学领域,理论与实践已经彻底脱离到以至于只能依赖智子放松微观现象的窗口才能进一步前进的地步,哪怕任何一个小小的概率的偏移都将导致新理论的彻底崩盘改写,近乎达到了玄学的地步,于是科学家们轻易的被智子锁死的现实所打败彻底失去信心一个个选择自杀。而作为应用科技的纳米技术依然是一个人类可以看得见摸的着的实实在在的技术反而不那么被左右。那么三体唯一能够做的就是使用怪力乱神的手段动摇汪淼的信念,而恰恰是他的执着的信念能够支撑住他成为ie人类击败三体的一个悍将。
    上善若水纳百川,
    纳米英才拯人寰。
    只因心中有执念,
    不惧怪神不信仙。
    

七月二十三日 等待变化等待机会

  1. 赞章北海
    
    北冥有海养鲲鹏,
    壮怀广宇欲远征。
    坚毅隐忍堪大用,
    百年蛰伏待东风。
    
  2. 公园散步偶题
    
    斜阳清风凉胜秋,
    啾啾鸟鸣在枝头。
    良辰美景应无憾,
    何事心头有隐忧?
    
    老爸步韵有诗曰
    
    大暑烈日盼凉秋,
    知了热燥响枝头。
    过好当下无遗憾,
    人无远虑有近忧。
    
  3. 叹《三体》悲剧人物杨冬。 ----人物名字暗含"冬日残阳",隐喻她是主人公叶文洁对于人类社会最后一点希望如寒冬残阳般的一点点若有若无的暖意,而当女儿杨冬因为智子干扰对撞机结果锁死人类科技进步后绝望自杀后也就失去对人世最后的希望而选择毁灭人类的道路。
    
    寒冬残阳遗腹子,
    与世隔绝如白纸。
    白桦作纸树当床,
    一生献给纯物理。
    微观探究有边界,
    智子干扰对撞机。
    物理规律不存在,
    信念崩溃选择死。
    
    又一首改了改
    
    寒冬残阳遗腹子,
    与世隔绝如白纸。
    白桦作纸树当床,
    量子物理对撞机。
    微观探究有边界,
    智子干扰来锁死。
    物理规律不存在,
    信念崩溃无所依。
    
  4. 《这条街》
    
    花开花落何时了,
    阴晴圆缺难舍抛。
    时过境迁怎可追,
    沧海桑田人已老。
    
  5. 下载了一个《道德经》的版本。
    	
    【道德经】
    
    (1)第一章
    
    道可道也 非恒道也 名可名也 非恒名也
    
    无 名天地之始也 有 名万物之母也
    
    故 恒无 欲以观其眇也 恒有 欲以观其所徼也
    
    两者同出 异名同谓 玄之又玄 众妙之门
    
    (2)第二章
    
    天下皆知美之为美 恶已 皆知善之为善 斯不善矣
    
    有无之相生也 难易之相成也 长短之相形也 高下相盈也 音声之相和也,先后之相随 恒也
    
    是以圣人居无为之事 行不言之教 万物作而弗始也 为而弗恃也 成功而弗居也夫唯弗居 是以弗去
    
    (3)第三章
    
    不上贤 使民不争 不贵难得之货 使民不为盗 不见可欲 使民不乱
    
    是以圣人之治也 虚其心 实其腹 弱其志 强其骨 恒使民无知无欲也
    
    使夫知不敢 弗为而已 则无不治矣
    
    (4)第四章
    
    道冲 而用之有弗盈也 渊兮 似万物之宗
    
    挫其锐 解其纷 和其光 同其尘 湛兮 似或存 吾不知其谁之子 象帝之先
    
    (5)第五章
    
    天地不仁 以万物为刍狗
    
    圣 人不仁 以百姓为刍狗
    
    天地之间 其犹橐龠乎 虚而不屈 动而愈出 多闻数穷 不如守于中
    
    (6)第六章
    
    谷神不死 是谓玄牝
    
    玄牝之门 是谓天地之根 绵绵呵 其若存 用之不堇
    
    (7)第七章
    
    天长 地久
    
    天地之所以能长且久者 以其不自生也 故能长生
    
    是以圣人后其身而身先 外其身而身存 不以其无私邪 故能成其私
    
    (8)第八章
    
    上善 若水
    
    水善 利万物而有静 居众人之所恶 故几于道矣
    
    居善地 心善渊 予善天 言善信 正善治 事善能 动善时 夫唯不争 故无尤
    
    (9)第九章
    
    持而盈之 不如其已 揣而锐之 不可长葆也 金玉盈室 莫之能守也 富贵而骄 自遗咎也 功遂身退 天之道也
    
    (10)第十章
    
    戴营魄抱一 能毋离乎
    
    槫气致柔 能婴儿乎
    
    修除玄监 能无疵乎
    
    爱民活国 能毋以知乎
    
    天门启阖 能为雌乎
    
    明白四达 能毋以知乎
    
    生之 畜之 生而弗有 长而弗宰 是谓玄德
    
    (11)第十一章
    
    卅辐同一毂 当其无 有车之用也
    
    埏埴而为器 当其无 埴器之用也
    
    凿户牖 当其无 有室之用也
    
    故 有之以为利 无之以为用
    
    (12)第十二章
    
    五色使人之目盲 五音使人之耳聋 五味使人之口爽 驰骋畋猎使人心发狂 难得之货使人之行方
    
    是以圣人之治也 为腹而不为目 故去彼而取此
    
    (13)第十三章
    
    宠辱若惊 贵大患若身
    
    何谓宠辱若惊 宠之为下也 得之若惊 失之若惊 是谓宠辱若惊
    
    何谓贵大患若身 吾所以有大患者 为吾有身 及吾无身 有何患
    
    故贵为身以为天下 若可托天下 爱己身以为天下 女何以寄天下
    
    (14)第十四章
    
    视之而弗见 名之曰微 听之而弗闻 名之曰希 捪之而弗得 名之曰夷 三者不可致诘 故混而为一
    
    一者 其上不谬 其下不惚 寻寻呵 不可名也 复归于无物 是谓无状之状 无物之象 是谓沕望 随而不见其后 迎而不见其首 执今之道 以御今之有 以知古始 是谓道纪
    
    (15)第十五章
    
    古之善为道者 微眇玄达 深不可志
    
    夫唯不可志 故强为之容 曰 与呵 其若冬涉水 犹呵 其若畏四邻 俨呵 其若客 涣呵 其若冰泽沌呵 其若朴 湷呵 其若浊 旷呵 其若谷
    
    浊而静之徐清 安以动之 徐生 保此道者不欲盈 夫唯不欲盈 故能蔽而不成
    
    (16)第十六章
    
    致虚 极也 守静 督也 万物旁作 吾以观其复也
    
    天物芸芸 各复归于其根 曰静 静 是谓复命
    
    复命 常也 知常 明也 不知常 妄 妄作 凶
    
    知常 容 容乃公 公乃王 王乃天 天乃道 道乃久 殁身不殆
    
    (17)第十七章
    
    太上 下知有之 其次 亲誉之 其次 畏之 其下 侮之
    
    信不足 案有不信 猷呵 其贵言也 功成事遂 而百姓皆谓我自然
    
    (18)第十八章
    
    故大道废 案有仁义 知慧出 案有大伪 六亲不和 案有孝慈 邦家昏乱 案有贞臣
    
    (19)第十九章
    
    绝圣弃知 民利百倍 绝仁弃义 民复孝慈 绝巧弃利 盗贼无有
    
    此三言也 以为文未足 故令之有所属 见素抱朴 少思寡欲 绝学无忧
    
    (20)第二十章
    
    唯与诃 其相去几何 美与恶 其相去何若 人之所畏 亦不可以不畏人
    
    望呵 其未央才 众人熙熙 若享于太牢 而春登台 我泊焉未佻 若婴儿之未咳
    
    累呵 如无所归 众人皆有余 我独遗 我愚人之心也 惷惷呵 鬻人察察 我独闵闵呵 忽呵 其若海 望呵 其若无所止 众人皆有以 我独顽以鄙 吾欲独异于人 而贵食母
    
    (21)第二十一章
    
    孔德之容 唯道是从
    
    道之物 唯望唯惚 惚呵 望呵 中有象呵 望呵 惚呵 中有物呵 幽呵 冥呵 中有请呵
    
    其请甚真 其中有信 自今及古 其名不去 以顺众父 吾何以知众父之然 以此
    
    (22)第二十二章
    
    炊者不立 自视者不章 自见者不明 自伐者无功 自矜者不长 其在道也 曰 余食赘行 物或恶之 故有欲者弗居
    
    (23)第二十三章
    
    曲则全 枉则直 洼则盈 敝则新 少则得 多则惑
    
    是以圣人执一以为天下式 不自视故章 不自现故明 不自伐故有功 弗矜故能长 夫唯不争 故莫能与之争 古之所谓曲则全者 几语才 诚全归之
    
    (24)第二十四章
    
    希言自然 飘风不冬朝 暴雨不终日 孰为此 天地而弗能久 而况于人乎
    
    故从事于道者同于道 德者同于德 失者同于失
    
    同于德者 道亦德之 同于失者 道亦失之
    
    (25)第二十五章
    
    有物混成 先天地生 萧呵 寥呵 独立而不改 可以为天地母 吾未知其名 字之曰道 强为之名曰大
    
    大曰逝 逝曰远 远曰反
    
    道大 天大 地大 王亦大
    
    国中有四大 而王居其一焉
    
    人法地 地法天 天法道 道法自然
    
    (26)第二十六章
    
    重为轻根 静为躁君 是以君子终日行不离其辎重 唯有环官 燕处则昭若 若何万乘之主而以身轻天下 轻则失本 躁则失君
    
    (27)第二十七章
    
    善行者无辙迹 善言者无瑕谪 善数者不以筹策 善闭者无关楗而不可启也 善结者无绳约而不可解也 是以圣人恒善救人 而无弃人 物无弃财 是谓袭明
    
    故 善人 善人之师 不善人 善人之资也
    
    不贵其师 不爱其资 虽知乎大迷 是谓眇要
    
    (28)第二十八章
    
    知其雄 守其雌 为天下溪
    
    为天下溪 恒德不离 恒德不离 复归于婴儿
    
    知其荣 守其辱 为天下谷
    
    为天下谷 恒德乃足 恒德乃足 复归于朴
    
    知其白 守其黑 为天下式
    
    为天下式 恒德不忒 恒德不忒 复归于无极
    
    朴散则为器 圣人用之则为官长 夫大制无割
    
    (29)第二十九章
    
    将欲取天下而为之 吾见其弗得已
    
    夫天下 神器也 非可为者也
    
    为者败之 执者失之
    
    物或行或随 或嘘或吹 或强或羸 或陪或隳 是以圣人去甚 去太 去奢
    
    (30)第三十章
    
    以道佐人主 不以兵强于天下 其事好还
    
    师之所居 楚棘生之 善者果而已矣 不以取强焉
    
    果而勿娇 果而勿矜 果而勿伐 果而不得已 居是 谓果而不强
    
    物壮则老 谓之不道 不道蚤已
    
    (31)第三十一章
    
    夫兵者 不祥之器也 物或恶之 故有欲者弗居
    
    君子居则贵左 用兵则贵右 故兵者 非君子之器也
    
    兵者 不祥之器也 不得已而用之
    
    铦袭为上 勿美也 若美之 是乐杀人也
    
    夫乐杀人 不可以得志于天下矣 是以吉事上左 丧事上右 是以偏将军居左 上将军居右 言以丧礼居之也 杀人众 以悲哀泣之 战胜 以丧礼处之
    
    (32)第三十二章
    
    道恒无名 朴虽小 而天下弗敢臣 侯王若能守之 万物将自宾 天地相合 以俞甘露 民莫之令 而自均焉
    
    始制有名 名亦既有 夫亦将知止 知止所以不殆
    
    俾道之在天下也 猷小谷之于江海也
    
    (33)第三十三章
    
    知人者 知也 自知者 明也 胜人者 有力也 自胜者 强也 知足者 富也 强行者 有志也 不失其所者 久也 死而不忘者 寿也
    
    (34)第三十四章
    
    道泛兮 其可左右也 成功遂事而弗名有也
    
    万物归焉而弗为主 则恒无欲也 可名于小
    
    万物归焉而弗为主 可名于大
    
    是以圣人之能成大也 以其不为大也 故能成大
    
    (35)第三十五章
    
    执大象 天下往 往而不害 安平泰 乐与饵 过格止 故道之出言 曰 淡呵 其无味也 视之 不足见也 听之 不足闻也 用之 不可既也
    
    (36)第三十六章
    
    将欲拾之 必古张之 将欲弱之 必古强之 将欲去之 必古兴之 将欲夺之 必古与之 是谓微明
    
    柔弱胜刚强 鱼不脱于渊 邦利器不可以示人
    
    (37)第三十七章
    
    道恒无名 侯王若能守之 万物将自化
    
    化而欲作 吾将阗之以无名之朴 夫将不欲 不欲以静 天地将自正
    
    (38)第三十八章
    
    上德不德 是以有德 下德不失德 是以无德
    
    上德无为 而无以为也 下德为之 而有以为
    
    上仁为之 而无以为也 上义为之 而有以为也
    
    上礼为之 而莫之应也 则攘臂而扔之
    
    故 失道而后德 失德而后仁 失仁而后义 失义而后礼
    
    夫礼者 忠信之薄也 而乱之首也
    
    前识者 道之华也 而愚之首也
    
    是以大丈夫居其厚而不居其薄 居其实而不居其华 故去彼而取此
    
    (39)第三十九章
    
    昔之得一者 天得一以清 地得一以宁 神得一以灵 谷得一以盈 万物得一以生 侯得一以为天下正?
    
    其致之也 谓天毋已清 将恐裂 谓地毋已宁 将恐废 谓神毋已灵 将恐歇 谓谷毋已盈 将恐竭 谓万物毋已生 将恐灭 谓侯王毋已贵以高 将恐蹶
    
    故 必贵而以贱为本 必高矣而以下为基 夫是以 侯王自谓 孤 寡 不谷 此其贱之本与 非也?
    
    故致数与无与 是故不欲禄禄若玉 珞珞若石
    
    (40)第四十章
    
    上士闻道 堇能行之 中士闻道 若存若亡 下士闻道 大笑之
    
    弗笑不足以为道 是以建言有之曰 明道如费 进道如退 夷道如类 上德如谷 大白如辱 广德如不足 
    建德如偷 质真如渝 大方无隅 大器晚成 大音希声 天象无刑 道褒无名 夫唯道? 善始且善成
    
    (41)第四十一章
    
    反也者 道之动也 弱也者 道之用也 天下之物生于有 有生于无
    
    (42)第四十二章
    
    道生一 一生二 二生三 三生万物
    
    万物负阴而抱阳 中气以为和 天下之所恶 唯孤 寡 不谷 而王公以自名也
    
    物或损之而益 益之而损 故人之所教 亦议而教人
    
    故强梁者不得死 我将以为学父
    
    (43)第四十三章
    
    天下之至柔 驰骋于天下之至坚 无有入于无间 吾是以知无为之有益也
    
    不言之教 无为之益 天下希能及之矣
    
    (44)第四十四章
    
    名与身孰亲 身与货孰多 得与亡孰病
    
    甚爱必大费 多藏必厚亡 故知足不辱 知止不殆 可以长久
    
    (45)第四十五章
    
    大成若缺 其用不弊 大盈若冲 其用不穷 大直如诎 大巧如拙 大辩如讷 躁胜寒 靓胜炅 请静可以为天下正
    
    (46)第四十六章
    
    天下有道 却走马以粪 天下无道 戎马生于郊
    
    罪莫大于可欲 祸莫大于不知足 咎莫憯于欲得 故知足之足 恒足矣
    
    (47)第四十七章
    
    不出于户 以知天下 不窥于牖 以知天道 其出也弥远 其知也弥少
    
    是以圣人不行而知 不见而明 弗为而成
    
    (48)第四十八章
    
    为学者日益 为道者日损 损之有损 以至于无为 无为而无不为
    
    将欲取天下也 恒无事 及其有事也 又不足以取天下矣
    
    (49)第四十九章
    
    圣人恒无心 以百姓之心为心 善者善之 不善者亦善之 德善也 信者信之 不信者亦信之 德信也
    
    圣人之在天下 詥焉 为天下浑心 百姓皆属耳目焉 圣人皆孩之
    
    (50)第五十章
    
    出生 入死 生之徒十有三 死之徒十有三 而人之生 动皆之死地之十有三 夫何故也 以其生生之厚也
    
    盖闻善执生者 陵行不遇兕虎 入军不被甲兵 兕无所揣其角 虎无所措其蚤 兵无所容其刃 夫何故也 以其无死地焉
    
    (51)第五十一章
    
    道生之 而德畜之 物刑之 而器成之 是以万物尊道而贵德
    
    道之尊 德之贵也 夫莫之爵而恒自然也
    
    道生之 畜之 长之 育之 亭之 毒之 养之 覆之 生而弗有也 为而弗恃也 长而弗宰也 此之谓玄德
    
    (52)第五十二章
    
    天下有始 以为天下母 既得其母 以知其子 既知其子 复守其母 没身不殆
    
    塞其兑 闭其门 终身不堇 启其兑 济其事 终身不棘
    
    见常曰明 守柔曰强 用其光 复归其明 毋遗身殃 是谓袭常
    
    (53)第五十三章
    
    使我介然有知也 行于大道 唯施是畏
    
    大道甚夷 民甚好解
    
    朝甚除 田甚芜 仓甚虚 服文采 带利剑 厌食而财货有余 是谓盗夸 盗夸 非道也
    
    (54)第五十四章
    
    善建者不拔 善抱者不脱 子孙以祭祀不绝
    
    修之身 其德乃真 修之家 其德有余 修之乡 其德乃长 修之国 其德乃夆 修之于天下 其德乃博
    
    以身观身 以家观家 以乡观乡 以邦观邦 以天下观天下 吾何以知天下然兹 以此
    
    (55)第五十五章
    
    含德之厚者 比于赤子 蜂虿虺蛇弗蜇 攫鸟猛兽弗搏 骨弱筋柔而握固 未知牝牡之会而脧怒? 精之至也 终日号而不耰 和之至也
    
    知和曰常 知常曰明 益生曰祥 心使气曰强 物壮则老 谓之不道 不道早亡
    
    (56)第五十六章
    
    知者弗言 言者弗知
    
    塞其兑 闭其门 和其光 同其尘 挫其锐 解其纷 是谓玄同
    
    故 不可得而亲 不可得而疏 不可得而利 亦不可得而害 不可得而贵 亦不可得而贱 故为天下贵
    
    (57)第五十七章
    
    以正治邦 以奇用兵 以无事取天下
    
    吾何以知其然哉
    
    夫天下多忌讳 而民弥贫 人多利器 而国家滋昏 民多伎能 而奇物滋起 法物滋彰 盗贼多有
    
    是以圣人之言曰 我无为也 而民自化 我好静 而民自正 我无事 而民自富 我欲无欲 而民自朴
    
    (58)第五十八章
    
    其政闵闵 其民屯屯 其正察察 其邦缺缺
    
    祸 福之所倚 福 祸之所伏 孰知其极
    
    其无正也 正复为奇 善复为妖 人之悉也 其日固久矣 是以方而不割 兼而不刺 直而不绁? 光而不眺
    
    (59)第五十九章
    
    治人 事天 莫若啬
    
    夫唯啬 是以蚤服 蚤服是谓重积德 重积德则无不克 无不克则莫知其极 莫知其极 可以有国 有国之母 可以长久 是谓深根固氐 长生久视之道也
    
    (60)第六十章
    
    治大国若烹小鲜
    
    以道莅天下 其鬼不神 非其鬼不神也 其神不伤人也 非其神不伤人也 圣人亦弗伤也 夫两不相伤 故德交归焉
    
    (61)第六十一章
    
    大邦者 下流也 天下之牝 天下之交也
    
    牝恒以静胜牡 为其静也 故大邦以下小邦 则取小国 小邦以下大邦 则取于大邦
    
    故 或下以取 或下而取 故 大邦者不过欲兼畜人 小邦者不过欲入事人 夫皆得其欲 则大者宜为下
    
    (62)第六十二章
    
    道者 万物之注也 善人之宝也 不善人之所保也
    
    美言可以市 尊行可以贺人
    
    人之不善 何弃之有 故立天子 置三卿 虽有拱之璧以先驷马 不若坐进此道
    
    古之所以贵此道者何也 不谓求以得 有罪以免与 故为天下贵
    
    (63)第六十三章
    
    为无为 事无事 味无味 大小 多少 报怨以德
    
    图难乎其易也 为大乎其细也 天下之难作于易 天下之大作于细 是以圣人终不为大 故能成其大
    
    夫轻诺必寡信 多易必多难 是以圣人猷难之 故终于无难
    
    (64)第六十四章
    
    其安也 易持也 其未兆也 易谋也 其脆易判 其微易散
    
    为之于其未有 治之于其未乱 合抱之木 作于毫末 九成之台 起于蔂土 百仞之高 始于足下 为之者败之 执者失之
    
    是以圣人无为也 故无败也 无执也 故无失也
    
    民之从事也 恒于几成而败之 慎终若始 则无败事矣 是以圣人欲不欲 而不贵难得之货 学不学 而复众人之所过 能辅万物之自然而弗敢为
    
    (65)第六十五章
    
    故曰 为道者非以明民也 将以愚之也
    
    民之难治也 以其知也 故以知知邦 邦之贼也 以不知知邦 邦之德也 恒知此两者亦稽式也?
    
    恒知稽式 此谓玄德 玄德深矣 远矣 与物反矣 乃至大顺
    
    (66)第六十六章
    
    江海之所以能为百谷王者 以其善下之也 故能为百谷王
    
    是以圣人之欲上民也 必以其言下之 其欲先民也 必以其身后之
    
    故居前而民弗害也 居上而民弗重也 天下乐推而弗厌也 非以其无诤与 故天下莫能与争
    
    (67)第六十七章
    
    小邦 寡民 使有什佰人之器而毋用 使民重死而远徙 有车舟无所乘之 有甲兵无所陈之 使民复结绳而用之 甘其食 美其服 乐其俗 安其居 邻邦相望 鸡犬之声相闻 民至老死不相往来
    
    (68)第六十八章
    
    信言不美 美言不信 知者不博 博者不知 善者不多 多者不善
    
    圣人无积 既以为人 己愈有 既以予人 己愈多
    
    故 天之道 利而不害 人之道 为而弗争
    
    (69)第六十九章
    
    天下皆谓我大 大而不宵 夫唯不宵 故能大 若宵 久矣其细也夫
    
    我恒有三宝 持而保之 一曰慈 二曰俭 三曰不敢为天下先
    
    慈 故能勇 俭 故能广 不敢为天下先 故能成事长 今舍其慈 且勇 舍其俭 且广 舍其后 且先 则必死矣 夫慈 以战则胜 以守则固 天将健之 如以慈垣之
    
    (70)第七十章
    
    善为士者不武 善战者不怒 善胜敌者弗与 善用人者为之下 是谓不诤之德 是谓用人 是谓肥天 古之极也
    
    (71)第七十一章
    
    用兵有言曰 吾不敢为主而为客 吾不进寸而退尺 是谓行无行 攘无臂 执无兵 乃无敌矣
    
    祸莫大于无敌 无敌近亡吾葆矣 故乘兵相若 则哀者胜矣
    
    (72)第七十二章
    
    吾言甚易知也 甚易行也 而人莫之能知也 而莫之能行也
    
    言有君 事有宗 其唯无知也 是以不我知 知者希 则我贵矣 是以圣人被褐而怀玉
    
    (73)第七十三章
    
    知不知 尚矣 不知不知 病矣 是以圣人之不病 以其病病也 是以不病
    
    (74)第七十四章
    
    民之不畏畏 则大畏将至矣
    
    毋闸其所居 毋猒其所生
    
    夫唯弗猒 是以不厌 是以圣人自知而不自见也 自爱而不自贵也 故去彼取此
    
    (75)第七十五章
    
    勇于敢者则杀 勇于不敢者则活 此两者 或利或害 天之所恶 孰知其故
    
    天之道 不战而善胜 不言而善应 不召而自来 单而善谋 天网恢恢 疏而不失
    
    (76)第七十六章
    
    若民恒且不畏死 奈何以死惧之
    
    若民恒且畏死 而为畸者吾将得而杀之 夫孰敢矣
    
    若民恒且必畏死 则恒有司杀者
    
    夫代司杀者杀 是代大匠斫也 夫代大匠斫者 稀有不伤其手矣
    
    (77)第七十七章
    
    民之饥也 以其上食税之多也 是以饥
    
    百姓之不治也 以其上之有以为也 是以不治
    
    民之轻死也 以其求生之厚也 是以轻死
    
    夫唯无以生为者 是贤贵生
    
    (78)第七十八章
    
    人之生也柔弱 其死也月亘坚强 万物草木之生也柔脆 其死也枯槁
    
    故曰 坚强者 死之徒也 柔弱微细者 生之徒也
    
    是以兵强则不胜 木强则恒 强大居下 柔弱细微居上
    
    (79)第七十九章
    
    天之道 犹张弓者也 高者印之 下者举之 有余者损之 不足者补之
    
    故 天之道 损有余而益不足 人之道则不然 损不足而奉有余?
    
    孰能有余而有以取奉于天者乎 唯又道者乎
    
    是以圣人为而不恃 功成而不居也 若此 其不欲见贤也
    
    (80)第八十章
    
    天下莫柔弱于水 而攻坚强者莫之能先也 以其无以易之也
    
    水之胜刚也 弱之胜强也 天下莫弗知也 而莫之能行也 故圣人言云曰 受邦之垢 是谓社稷之主 受邦之不祥 是为天下之王 正言若反
    
    (81)第八十一章
    
    和大怨 必有余怨 焉可以为善 是以圣人右介 而不以责于人
    
    故 有德司介 无德司彻 夫天道无亲 恒与善人
    
  6. 下载了一个vscode的shortcut,值得保存打印。
  7. 这个是改进的版本
    1. This is textbook meta function to calculate number of argument of a function pointer or reference。为了应付函数指针引用和所谓的值所以只能做不同的特例化
    2. a "smart" invoke that calculate correct number of argument and invoke这个是改进的版本因为不再假定返回值类型的通用函数了
    3. 其实,最困难的部分是在如何调用一个tuple里的函数指针在给它智能的匹配参数还能够最先返回结果,之前我实际上是实现了一遍std::apply而正确的做法还是不要自己实现而是写一个合适的lambda来传入参数。这个才是最难的。那么我们先假定我们所有的函数都是返回一个通用的结果如下
    4. 那么这样子我们就可以定义这样的神奇函数,这里我们使用的是folder expression,它有两个方面,首先,它是可以保证我们收集每一个顺序执行的结果,其次,因为是的结果可以保证当第一个函数返回非真的时候返回,否则普通的apply的lambda就是顺序执行所有的函数指针,这个显然不是我们想要的。
  8. 某日吃完晚饭散步打酱油若干首
    俄乌前线战火飞,
    ​生灵涂炭瓦砾堆。
    ​总怪普京路子野,
    ​其实拜登幕后推。
    
    夏至随风至,
    ​小暑来的迟。
    ​全球同凉热,
    ​我意言于此。
    
    
    ​烈日当头催,
    ​草衰鹰高飞。
    酷暑连三月,
    旱地盼惊雷。
    
    ​
    英伦日暮有余辉,
    ​美帝迟早路不归。
    ​不如指点江山事,
    ​鸡虫小事莫理会。
    
    ​白鹭振翅飞,
    ​疑是被人追。
    ​举手搔白头,
    ​脚下岁月催。
    
    ​夏至花开蜂蝶追,
    ​浓香袭远入心扉。
    ​有心摘花忧人怨,
    ​借口明日大风吹。
    
  9. 某日看到同学的朋友圈有感
    云卷云舒处,
    碧海入画图。
    若非人约后,
    怎会景色殊?
    
  10. 又一日看到同学朋友圈野营有感
    小径穿林间,
    荷叶浮水闲。
    溪水拍岸处,
    篝火伴人眠。
    
    老爸说:儿子,我喜欢后一首,诗景画意尽涵盖其中,和几句助兴。
    还需书一本,
    茶当酒一樽。
    溪水潺潺流,
    山风习习吻。
    深山如仙境,
    忘了是凡尘。
    
  11. 某日有感
    网红生活才心酸,
    直播带货辛苦钱。
    一年三百六十五,
    不曾偷的一刻闲。
    
    某日吃的很饱撑的厉害
    今天晚上吃完饭,
    我独自一人去公园,
    数着脚下的红砖,
    心里想着乌克兰,
    俄乌战火好几番,
    望眼全球都不安,
    日本死了个安倍晋三,
    大英帝国约翰逊也被罢了官。
    地球每天都在转,
    可是世界每天都不一般。
    我虽然每天能吃饱饭,
    还是想把闲事管一管。
    拜登老头需要正三观,
    普京大帝也不要老把邻居的事情来管,
    中国除了经济也要发展全,
    各个宗教不要搞异端。
    这样天下才不乱,
    想到这里我还是把家转。
    
  12. 想到《三体》里最让人气愤的人物就是程心
    程心
    
    福至心诚圣母心,
    孰料三体本无情。
    心软难堪大才用,
    地球往事漂流瓶。
    
  13. 而三体的主人公要怎么评价呢?
    罗辑
    
    放浪形骸本无稽,
    宇宙真理唯逻辑。
    面壁执剑一甲子,
    石刻历史写传奇。
    
  14. 难道他不也值得你的纪念吗?
    丁仪
    
    甲乙丙丁排第一,
    太空船里捉水滴。
    智子锁死加速器,
    百岁教授教物理。
    
  15. 实际上最招人喜欢的是他,因为人类都喜欢强者
    赞史强
    
    杀伐决断活阎罗,
    智计无双狠角色。
    一物本来降一物,
    恶人自有恶人磨。
    
  16. 最后是和同学的对话,我想我写的再多也只能算是顺口溜,可能连诗都算不上吧?
    子建七步咏名诗,
    我打酱油无人知。
    其实都是顺口溜,
    没啥真才与学识。
    
  17. 早上看到的不明觉厉的帖子,还没有开始看,以前只知道rpath,可是rpath-link是什么?

七月三十一日 等待变化等待机会

  1. 某日散步看到雁南归有感
    天高云自淡,
    风轻气更闲。
    一路南归雁,
    没入天际间。
    
  2. 我爸爸看了认为厦门季节不对还是三伏天,所以我才感叹
    厦门酷暑热翻天,
    何塞已见雁南迁。
    环球一时两季景,
    四月桃花有新篇。
    
    取自
    人间四月芳菲尽,
    山寺桃花始盛开。
    
    的典故
  3. 酷暑未消雁南迁,
    思乡心切离弦箭。
    久在异乡为异客,
    故乡山水能不念?
    
  4. crazy Nancy要去台湾有感而发
    台海起风云,
    实力是后盾。
    想打我就打,
    看谁是徒孙!
    
    台岛战云堆,
    妖婆行踪祟。
    东风破迷雾,
    看谁把牛吹。
    
  5. 黑云压顶塞胸臆,
    暴雨未至洒水滴。
    万籁具寂等惊雷,
    无知小鸟叫吱吱。
    

八月十四日 等待变化等待机会

  1. 今日有同学来湾区一聚,期间谈论国事,意见不尽相同,私以为眼界当放长远,毕竟国策施行没有尽善尽美之可能,然则不应一时一事而否定,有感而发,口占一绝:
    雄才大略起宏图,
    百年不遇前路殊。
    庶民岂知前程险,
    伏枥老马才识途。
    
    同意你的观点,香港大乱而后治,佩窜台,相互做局,最后还是道比魔高。两件事都出乎常人所料,令人信服。不战而屈人之兵,善之善者也。故上兵伐谋,其次伐交,其次伐兵,其下攻城。攻城之法,为不得已而为之。
    香江台岛三年间,
    魑魅魍魉舞翩跹。
    若非胸存宏图志,
    岂能意定如等闲。
    
  2. 关于和你太太的争论有几句话冒着被人咒骂也实在是不吐不快。
    北客南来谈疫情,
    愤懑不平溢于心。
    人生除死无大事,
    劝君将心比他心。
    
  3. ...略。临别赠诗一首云云。
    有朋北来相聚欢,
    三十一载弹指间,
    钢琴夜宴一席酒,
    峥嵘岁月两鬓边。
    
  4. 又有感而发
    无题
    
    一条真理占中间,
    世人观点分两边,
    为政但听百家言,
    大国治理烹小鲜。
    
    可是正话也可反说,孰是孰非殊难判定:
    
    真理通常走中间,
    三观不正排两边,
    为政若听百家言,
    大国理政蒸炒煎。
    
    所谓被称之为“艺术”的很多行当都是所谓的实践科学,没有什么一定之法,都是成者王侯败者寇的结果导向的经验主义,哪有什么放之四海而皆准的固有方法,就比如战争被称之为战争艺术就是因为变化多端扑朔迷离如同艺术一般的见仁见智,国家治理可能也是南橘北枳一般无法照搬照用,很可能就是如烹饪一般煎炒蒸炸各有巧妙不同。如今世界来到一个新的十字路口,人类的未来在哪里?

八月十六日 等待变化等待机会

  1. 红花满树枝,
    贪看不自持。
    残阳西沉久,
    新月东升迟。
    月移花重影,
    夜露沾衣湿。
    晓看落英处,
    相似不相识。
    
  2. 三十一年梦,
    近日得相逢。
    相见惊初见,
    把酒忆峥嵘。
    今宵一别后,
    明日关山重。
    待到重逢日,
    再叹岁月匆。
    

八月十七日 等待变化等待机会

  1. 半夜醒来抖音上的两句歌词始终萦绕耳际:
    	初闻不识曲中意,
    	再闻已是曲中人。
    
    于是改了一下:
    红花开满枝,
    贪看不自持。
    初闻不识味,
    再闻味难知。
    夕阳西下久,
    新月东升迟。
    晓看落英处,
    相似不相识。
    
    甚至于老布改的也不错,自成一派
    红花开满枝,
    贪看不自持。
    晓看落英处,
    相似不相识。
    

八月二十一日 等待变化等待机会

  1. 不过看来我想表达的两句歌词的意境很难被领会:
    初闻不识词中意,
    再闻已是词中人。
    
    这里用的是花香久闻而不觉其香味,可是在唯一开始能有机会品味的时候还阅历不够不能识别滋味,等到有能力体味花香的时候却因为久闻其味而不觉其味,以至于一生再也不能体验真正的花香是何味。这种人世的缺憾所在皆是也,因此才叹息。
    红花开满枝,
    贪看不自知。
    初闻不识味,
    再闻味全失。
    月移花影重,
    夜露沾衣湿。
    晓看落英处,
    相似不相识。
    
    这里最后一句"相似不相识"我的同学老布说有禅意,其实,他是纯理性的理解,没有领悟到文学的模糊,"相似"难道不是谐音"相思"吗?
    老布说“相思不相识”也有禅意。
  2. 我有一个上联
    蜜蜂采花酿蜂蜜
    
    我想了一个不太工整的下联
    海南造岛镇南海
    
  3. 看到远方的朋友云游四海心生羡慕口占一绝。
    蓝天白云播,
    碧海拨轻波。
    驱车圣乔治,
    海天都忘我。
    

八月二十四日 等待变化等待机会

  1. 老爸填的词《浪淘沙》
    《浪淘沙》 --庆幸厦门
    
    暑气消还久,
    虽悄来秋。
    酷热火海袭不休,
    滚滚热浪惹人愁,
    厦门稍有。
    
    风景这边秀,
    海上绿洲。
    阵阵海风凉依旧,
    花开鸟鸣琴声扬,
    福气天就。	
    
  2. 我试着改了几个字
    《浪淘沙》 --庆幸厦门
    	
    暑气消未久,
    欲去还留。
    酷热火海袭不休,
    滚滚热浪惹人愁,
    厦门少有。
    
    唯幸风景优,
    海上绿洲。
    阵阵海风凉依旧,
    花开鸟鸣琴声扬,
    福气天就。
    
  3. 步老爸的韵试作一首:
    《浪淘沙》--不舍夜昼 步老爸韵试作
    	
    大雁过加州,
    才来就走,
    排成人字鸣啾啾,
    一路向南不回头,
    雁去人留。
    
    杞人为天忧!
    为何发愁?
    雁来雁去总依旧,
    斗转星移何时休?
    不舍夜昼。 	
    

八月二十七日 等待变化等待机会

  1. 在《Book Club》里的分享《Lost In Translation》, 是关于三体的心得
  2. 步陆游《钗头凤》
    
    骚白首,
    常忆旧,
    为赋别情空折柳。
    谋生活,
    世道恶,
    十载漂泊,
    向谁述说?
    我,我,我。
    
    有近忧,
    空犯愁,
    世纪动荡在前头,
    叶未落,
    已知秋,
    抱薪救火,
    战事越多,
    错,错,错。
    
    另附宋代陆游的《钗头凤·红酥手》
    
    红酥手,
    黄縢酒,
    满城春色宫墙柳。
    东风恶,
    欢情薄。
    一怀愁绪,
    几年离索。
    错、错、错。
    
    春如旧,
    人空瘦,
    泪痕红浥鲛绡透。
    桃花落,
    闲池阁。
    山盟虽在,
    锦书难托。
    莫、莫、莫!
    

八月二十八日 等待变化等待机会

  1. 。。。所言极是,谨记了。然则有感则发,不吐不快,去健身房回来冲澡前偶得:
    曾记否,
    常聚首,
    觥筹交错向天吼。
    一无所有,
    终日奔波,
    错,错,错。
    
    赴异国,
    成异客,
    二十载漂泊蹉跎。
    几多欢乐,
    几多折磨,
    多,多,多。
    
  2.  思念久,
    白了首,
    故乡山水哪里有?
    梦里魂游,
    胸中丘壑,
    陌,陌,陌。
    
    思不得,
    心忐忑,
    异乡异国为异客。
    喜怒哀乐,
    与谁述说,
    我,我,我。
    

九月四日 等待变化等待机会

  1. 赞《西陆强军号》女子远火排实弹射击
    
    飒爽英姿火箭炮,
    西陆女兵真骄傲。
    毁天灭地长空啸,
    尘沙万里染征袍。
    
  2. 无题
    
    俏俏美娇娘,
    芳龄十八强。
    敢问家何处?
    遥指水一方。
    

九月十一日 等待变化等待机会

  1. 闻听俄乌前线混乱消息有感而发
    
    中秋才过天转凉,
    秋风一起满地黄。
    俄乌战火连六月,
    扑朔迷离战事僵。
    
  2. 备份微信档案无意中找到一首老爸几年前的词:
    《清平乐》---爬山 2016-06-19
    
    天蓝云淡,荒草连不断
    几十里外赶爬山,何惧没吃早饭?
    呼呼登上顶峰,享尽硅谷晨风。
    遥想热茶在手,细尝上海小笼。
    

九月十八日 等待变化等待机会

  1. 去健身房出来惊见满地湿,一场秋雨不期而至有感而发
    
    秋雨刹至无心机,
    漫撒珠泪地略湿。
    秋风此来传冬信,
    漫天风雪不可知。 
    
  2. 夜遇重阳真人传我修炼口诀醒来遗忘大半,枯肠索肚仅录得几句不敢私藏特传予诸位
    
    大道歌
    
    求道贵守一,
    修仙莫痴迷。
    天地本无物,
    阴阳合一体。
    阳气本来轻,
    阴气常沉底。
    阴升阳气降,
    全在一呼吸。
    吸气当用力,
    呼气绵又密。
    阳气储会元,
    命门要关闭。
    阴气走天元,
    有心却无意。
    阴阳求合体,
    心田无心机。
    有力却无气,
    有气也无意。
    绵绵又密密,
    万念具归一。 
    
  3. 秋雨无心至,
    落叶藏心机。
    高处总是寒,
    随遇靠身低。
    
  4. 同学朋友呼吁下周末爬山远足
    走走也无妨,
    主要看地方。
    有事也无事,
    天圆地更方。
    
  5. 世事也无常,
    前路莫彷徨。
    为睹彩虹日,
    风雨又何妨?
    
  6. 爬山乃大事,
    风雨怎可期?
    遥想登顶日,
    回眺众山低。
    
  7. 秋风抢入怀,
    闲坐看云海。
    任凭风云起,
    物我两不在。
    
  8. 秋风乍起柳叶飞,
    长椅独坐凭风吹。
    看罢风起看云涌,
    贯耳风声似惊雷。
    
  9. 阴云密布大风吹,
    漫卷愁绪有心回。
    本应秋风伴秋雨,
    有风无雨何须归?
    

九月二十一日 等待变化等待机会

  1. 红叶高岗秀,
    黄花深谷悠。
    芳菲凋谢后,
    潺潺小溪流。
    
  2. 残月不知羞,
    弯眉不曾修。
    冷眼旁观久,
    独目最识秋。
    
  3. 人生如梦醒无踪,
    不问缘由去如风。
    但叫心底存回忆,
    岁月匆匆也从容。
    
  4. 读美女诗人现代诗有感而发
    
    静夜忧思怪月圆,
    遥想当年鱼水欢。
    郎情妾意拥不够,
    转眼柔情似云烟。
    秋虫冬雪炊烟袅,
    春鸟夏蝉波光滟。
    君既抱躯投湖底,
    妾当飞身入火烟。
    
  5. 改编自美女诗人现代诗
    
    裙角微温淡体香,
    无奈君心已转凉。
    不觉流年偷换季,
    悠忽丹桂兰草芳。
    不远不近不着迹,
    不温不火初模样。
    心中愁绪才铺开,
    秋风捎信老地方。
    娓娓诉说藏心底,
    默默以对暗忧伤。
    人间朝暮总漂泊,
    叶落惊秋却惆怅。
    
  6. 雨过天晴云最丽,
    风吹湖皱水更清。
    	
    乌云疾风推,
    白云蓝天追。
    云开天晴了,
    斜阳送我归。
    
    天蓝不算事,
    云白未足奇。
    公园长椅侧,
    风吹柳腰低。
    

九月二十七日 等待变化等待机会

  1. 临晨去健身房路感风寒有感
    
    落叶最知秋,
    天凉心中忧。
    树高不可久,
    归根复何求?
    
  2. 落叶先知秋,
    天凉心内忧。
    高枝难长久,
    归根复何求?
    
  3. 落叶
    
    天凉秋叶忧,
    高枝岂长久。
    不待秋风扫,
    归根复何求?
    
  4. 秋叶
    
    秋叶不知忧,
    高枝盼长久。
    待到秋风起,
    归根才知秋。
    
  5. 秋叶(向东的作品)
    
    秋叶不知忧,
    高枝慕长久。
    待到寒风起,
    归根才知秋。
    
  6. 秋叶叹
    
    秋叶哪知忧,
    风中舞不休。
    待到疾风日,
    归根方知秋。
    
  7. 秋叶叹(春明改的好)
    
    秋叶可知忧,
    风中舞不休。
    待到疾风至,
    归根方知秋。
    
  8. 秋叶叹
    
    秋叶几曾忧?
    风起愁去留。
    待到疾风至,
    归根一世休。
    
  9. 秋叶叹
    
    天凉才知忧,
    早知不可留。
    枝高岂能久?
    归根万事休。
    
  10. 秋叶叹
    
    天凉叶最忧,
    枝高有何求?
    待到秋风至,
    归根最长久。
    
  11. 秋叶
    
    天凉叶知秋,
    风起更添忧。
    若为归根故,
    有何不可丢?
    
  12. 叹秋叶(改了几个字)
    
    天凉叶才忧,
    枝高不可留。
    何须待风起,
    归根最长久。
    
  13. 叹秋叶
    
    天凉也不忧,
    风起舞不休。
    怎料东风至,
    落地随处丢。
    
  14. 秋叶
    
    天凉就有忧,
    风起愁不休。
    枝高总落地,
    归根最长久。
    
  15. 叹秋叶
    
    秋叶哪知羞?
    位高求人留。
    无奈秋风扫,
    随风一路丢。
    
  16. 秋叶
    
    秋风起不休,
    秋叶哪知忧。
    待到归根日,
    才知最长久。
    
  17. 秋叶
    
    秋风起不休,
    秋叶却不忧。
    心知落地日,
    最是归根久。
    
  18. 秋叶
    
    风起也无忧,
    天凉亦无愁。
    总有归根日,
    枝高最难留。
    
  19. 秋叶叹
    
    天凉已知秋,
    枝高难久留。
    总有落地日,
    归根复何求?
    
  20. 看诗友评论启发改编
    
    秋风起啾啾,
    红叶飘悠悠。
    一道别离景,
    万里思乡愁。 
    
  21. 有朋北来在深秋,
    眼看大厦成危楼。
    帝国衰败百年后,
    我为杞人把天忧。
    

十月十五日 等待变化等待机会

  1. 偶尔听到一首悲伤的曲子,把歌词摘出来。
    
    白天不懂夜的黑,
    北雁执意向南飞。
    高歌一曲泪满把,
    秋风哪管落叶悲!
    
  2. 然而小儿女毕竟不知国仇家恨
    
    悲情一曲心已碎,
    撕心裂肺求一醉。
    歌者只晓儿女情,
    国破家亡哪知味?
    
  3. 老爸前些天发来的新作
    《鹧鸪天》
    桂花
    
    萧瑟秋深露新颜,
    粒粒簇簇花香远。
    陌边山坡随地长,
    不同菊花守庭园。
    
    傲冰霜,
    性清寒。
    蘭桂齐芳美名传。
    情疏迹远蕴高雅,
    静心依偎绿丛间。
    
  4. 秋日偶题
    
    晚秋晚风抚,
    云淡晚霞涂。
    秋叶埋小径,
    荒草掩归途。
    
    
    风轻不知足,
    云淡更轻浮。
    万木潇潇下,
    林间正踟蹰。
    
    风轻云驻足,
    云淡晚霞涂。
    俯身拾秋木,
    仰天观星图。 
    
  5. 看诗友诗有感而发
    
    北方有佳丽,
    长思渐成疾。
    芳颜摄心意,
    软语若兰息。
    登高望天际,
    低头数归期。
    心愿生双翼,
    一会胜良医。 
    

十月十六日 等待变化等待机会

  1. 公园散步偶题
    
    秋重风驻足,
    天阴云踌躇。
    鸟鸣树头噪,
    人行小径独。
    
    有时候看到的景色,心情是非常的矛盾,简单的用语言来表达,可能就是洋洋洒洒的上千字。如果用视频声音媒体的话,可能又是多少兆。而中文的简洁就是用一首五言律诗20个字来表达浓缩的情景抽象出来的感觉。 大背景是深秋,凝重的阴天。但是不冷也不热,没有阳光灿烂却也秋意绵绵。天阴不雨,树凝风静,虽然遍地可看到稀稀落落的落叶残枝,但总的色调又是树翠草青。公园里仿佛是安静的,到处能听到鸟鸣啾啾,静得听到自己的心跳。实际上背景隐隐能听到远处280高速上车声隆隆,天空偶尔飞过的飞机轰轰。只是一个宁静而嘈杂的混合体。 公园河边桥下,偶尔能看到无家可归的帐篷,垃圾隐隐约约从树丛草边隐现,而公园小径上又能看到一群身穿志愿者服饰的"白左"代表--"老白女"--在清理草坪垃圾整理环境。所以这是一个混乱与和谐的混合体。一方面是满目疮痍,不堪入目,一方面又是欣欣向荣,和谐宁静。
    树静秋风停,
    鸟鸣绿草新。
    寰宇无大事,
    天阴云更轻。
    
  2. 环宇无大事。真的吗?俄罗斯准备发动反击,乌克兰准备拖北约入场。这是一场乌克兰打不赢俄罗斯输不起的战争。俄罗斯输不起是因为普京已经把自己的政治生命绑定了这场战争,俄罗斯输不起是因为这个是它最后的反击,俄罗斯输不起也因为他坚信他不会输因为他有最后的手段,那就是核武器。乌克兰打不赢是因为他在开战之初就输了,乌克兰当初的目的就是加入欧盟过好日子,加入北约得到安全保证,而与俄罗斯开战就是南辕北辙,与其他国家交战并有领土争端就注定不可能加入欧盟和北约!另一方面是欧洲正在酝酿一场新的革命,一场席卷上个世纪的社会主义革命,一场意在推翻现行资本主义制度的革命。
    美国貌似还像前两次世界大战战一样,隔着两个大洋,静观世界乱局,岂不知美国已经是后院失火,自身的统治基础摇摇欲坠。全球有600多万亿的衍生金融品市场,在美联储的极速加息下存在着崩盘的危机貌似强大的中央银行可以无限印钞,岂不知多米诺骨牌,连锁反应的威力在于它来的时候是山洪海啸,根本不给你筑坝强堤的时间。你以为世界其他地方都是太平无事吗?中东,上一轮的伊斯兰颜色革命并没有结束,不彻底的改革在今天的世纪疫情与金融危机的双重打击下,要再次卷土重来。伊朗可能是继土耳其倒下的第2个。中东重新陷入混乱的可能性一天天增加。
    南美洲只是美国翅膀上粘黏的一只小树枝,在美联储伸展翅膀抖落灰尘的过程中,被无情的甩向深渊。非洲的战乱从来就没有停止过,东南亚以及南亚内各国的矛盾是固有与根深蒂固的。似乎世界的危局全部落在东亚某大国的行事方向上。因为一旦这个大国决定入场,那么他周围原本被压制住的矛盾都会浮现出来,高加索会重燃战火,东南亚也会各自为战,即便是远在天边的大洋洲,失去了经济上的依托也会进入混乱。
    所以现在只是暴风雨来临之前的暂时的宁静。海燕在波涛汹涌的悬崖边上,低低的翱翔,在一个闪电到来之前的瞬间,直挺挺的冲向天际,展开双翅向阴云密布的苍天发出怒吼,让暴风雨来得更猛烈一些吧
  3. 有感于霍去病空前绝后的盖世武功
    
    登高就登黄鹤楼,
    封侯只愿冠军候。
    千里闪击霍去病,
    天妒神驹不肯留。 
    

十月二十二日 等待变化等待机会

  1. 	
    大英女相特拉斯,
    上任月半把官辞。
    日暮帝国千疮孔,
    无米妄称铁娘子。
    
  2. 狂澜既倒秋梦碎,
    大厦已倾众人推。
    无奈流水落花去,
    苍天有眼饶过谁?
    

十月二十三日 等待变化等待机会

  1. 口占一绝
    
    夕阳如锦绣,
    微风拂衣袖。
    碧草配枫叶,
    坐看好个秋。
    
    老爸评点:口占一绝写得好!第三句不妨改成:枫叶落枯草,供参考。
    口占一绝
    
    夕阳如锦绣,
    微风拂衣袖。
    枫叶落枯草,
    坐看好个秋。
    
  2. 炫目夕阳心底暖,
    拂面微风不觉寒。
    异乡美景终将尽,
    北雁思乡向南迁。
    

十月二十七日 等待变化等待机会

  1. 《清平乐》--看美国
    
    死生看淡,
    没人提新冠。
    世界人民都惊叹,
    死了不过百万。
    
    昔日民主巅峰,
    今日遍吹东风。
    借债从不发愁,
    坐待海啸山崩。 
    
  2. 收到gcc的邮件通知这是一个所谓的metabug,就是很多类似的bug集合在一起,其中有很多都是我提的,可是我现在几乎都不明白是什么意思了。

十月二十八日 等待变化等待机会

  1. 看到地名有个RUSSIA不由得填首词献给普大帝
    《清平乐》--赞普京
    
    
    死生看淡,
    哪个不服就干。
    打不过就扔核弹,
    不过死两千万。
    
    装甲铁流隆隆,
    天上导弹轰轰。
    普京铁拳在手,
    笑看美帝装怂。 
    
  2. 一弯冷月天际悬,
    夜幕低垂望无边。
    嫦娥仙子挥橹篙,
    夜海茫茫划月船。
    

十一月四日 等待变化等待机会

  1. 天边染红霞,
    头顶明月挂。
    手头无彩笔,
    心中留图画。 
    
  2. 改了一下
    
    天边染红霞,
    明月当头挂。
    风景正当好,
    夕阳却西下。
    
  3. 《清平乐》
    
    风云变幻,
    全球格局变。
    世界经济都打乱,
    各国准备三战。
    
    美帝经济中空,
    困兽四处行凶。
    中国成竹在胸,
    放手一搏对攻。
    

十一月六日 等待变化等待机会

  1. 光似穿林箭,
    树如竖琴弦。
    自然有妙曲,
    心底暗暗弹。
    
  2. 老树昏鸦秋叶黄,
    过客小径绕老房。
    黑云压顶胸臆闷,
    树静风轻难久长。
    
  3. 雨冷霜露寒,
    层林似火燃。
    何须盼春至,
    秋色最斑斓。
    

十一月十二日 等待变化等待机会

  1. 《清平乐》--健身房即兴偶得
    
    风云变幻,
    俄乌正酣战。
    昔日兄弟兵戎见,
    美帝阴谋浮现。
    
    全力对我围攻,
    欧日澳印跟风。
    人民紧跟中共,
    看谁能与争锋?
    
  2. 我看写诗境界
    	
    通俗易懂顺口溜,
    合辙押韵打酱油。
    诗词本是真情露,
    平仄对仗再讲究。
    
  3. 我学作诗
    
    我学作诗两三年,
    词句多随真情现。
    纵使平仄有亏欠,
    真正方家不会嫌。
    
  4. 看诗友静夜思有感
    
    秋夜露寒欲何往?
    无心睡眠为谁忙?
    朦胧秋月茫茫夜,
    暗花浓香扰梦乡!
    
    
    附原作如下
    
    【七绝•月夜思】
    庭院无声伴露霜,
    挑灯夜战费思量。
    隔空遥看朦胧月,
    更漏深深沁暗香。
    
  5. 《清平乐》--赞掌舵人
    
    征途漫漫,
    撸起袖子干。
    百年复兴不能断,
    舵手岂能更换?
    
    美日全力围攻,
    历史责任更重。
    若非沧海横流,
    哪来伟业丰功最后一句我原来写作气势恢宏,后来觉得伟业丰功更贴切!
    
  6. 看同学家秋意盎然有感而发

    看看我家大连小区的秋色,你来首诗吧

    居高望四周,
    满眼都是秋。
    斑斓琉璃色,
    才是最温柔。
    
  7. 看诗友诗画有感
    
    《清平乐》--情思羁绊
    
    情思羁绊,
    相逢就不见。
    剪不断又理还乱,
    漫漫情路一段。
    
    感情如果输光,
    思绪如何丈量?
    季节就算拉长,
    岁月换回凄凉。 
    
  8. 看诗友回忆儿时记忆诗词有感
    
    冬月沉睡冬夜里,
    儿时记忆在心底。
    溪畔稻香引游鱼,
    梦中场景最清晰。 
    
  9. 白云有何罪?
    惨遭风揉碎!
    有心鸣不平,
    赤足把风追。 
    
  10. 美军在衰败,
    有何可悲哀?
    放弃霸权梦,
    世界充满爱。
    
  11. 疫情来势汹,
    其后去无踪。
    居家办公日,
    岂不更轻松?
    
  12. 看歼20珠海航展表演飞行有感
    
    长空龙城飞将真调皮,
    全靠技术够扎实。
    花样炫目吸眼球,
    人人看了都欢喜。
    
  13. 尝试创作新诗体,不妨就叫作"惊坐体"
    
    垂死病危惊坐起,
    日本武装了自己。
    
    垂死病危惊坐起,
    原来欧盟已解体。
    
    垂死病危惊坐起,
    美帝要动核武器。
    
    垂死病危惊坐起,
    周遭都是公知体。
    
    垂死病危惊坐起,
    中华复兴待统一。
    
    垂死病危惊坐起,
    百年屈辱中崛起。
    
    垂死病危惊坐起,
    列强竟是我自己。
    
    垂死病危惊坐起,
    世界命运共同体。
    
    垂死病危惊坐起,
    大毛二毛扭一起。
    
    垂死病危惊坐起,
    大毛病体不能敌。
    
    垂死病危惊坐起,
    大毛被围干着急。
    
    垂死病危惊坐起,
    二毛拿刀割自己。
    
    垂死病危惊坐起,
    日本赖账气死你。
    
    垂死病危惊坐起,
    美帝磨刀要干你。
    
    垂死病危惊坐起,
    美帝金融核武器。
    
    垂死病危惊坐起,
    公知横行美帝喜。
    
    垂死病危惊坐起,
    大战要打第三次。
    
    垂死病危惊坐起,
    欧洲缺少天然气。
    
    垂死病危惊坐起,
    全球制造出危机。
    
    垂死病危惊坐起,
    台湾收复待时机。
    
    垂死病危惊坐起,
    全球供应要分离。
    
    垂死病危惊坐起,
    自创一派惊坐体。
    
    垂死病危惊坐起,
    韩国痴迷比特币。
    
    垂死病危惊坐起,
    当今世界谁垂死。
    
    垂死病危惊坐起,
    百年崛起谁惊奇。
    
    垂死病危惊坐起,
    刚刚崩了比特币。
    
    垂死病危惊坐起,
    日本带了呼吸机。
    
    垂死病危惊坐起,
    美帝狂开印钞机。
    
    垂死病危惊坐起,
    珠海航展卖武器。
    
    垂死病危惊坐起,
    中国出口大飞机。
    
    垂死病危惊坐起,
    打仗要靠无人机。
    

十一月十六日 等待变化等待机会

  1. 千古美文《滕王阁序
    豫章故郡,洪都新府。星分翼轸,地接三江五湖,控蛮荆而引瓯越。物华天宝,龙光射牛斗之墟;人杰地灵,徐孺下陈蕃之榻。雄州雾列,俊采星驰。台隍枕夷夏之交,宾主尽东南之美。都督阎公之雅望,棨戟遥临;宇文新州之懿范襜帷暂驻。十旬休假,胜友如云;千里逢迎,高朋满座。腾蛟起凤,孟学士之词宗;紫电青霜,王将军之武库家君作宰路出名区;童子何知,躬逢胜
    这里是汉代的豫章郡城,如今是洪州的都督府,天上的方位属于翼、轸两星宿的分野,地上的位置连结着衡山和庐山。以三江为衣襟,以五湖为衣带,控制着楚地,连接着闽越。这里物产的华美,有如天降之宝,其光彩上冲牛斗之宿。这里的土地有灵秀之气,陈蕃专为徐孺设下几榻。洪州境内的建筑如云雾排列,有才能的人士如流星一般奔驰驱走。池据于中原与南夷的交界之处,宾客与主人包括了东南地区最优秀的人物。都督阎公,享有崇高的名望,远道来到洪州坐镇,宇文州牧,是美德的楷模,赴任途中在此暂留。每逢十日一旬的假期,来了很多的良友,迎接远客,高贵的朋友坐满了席位。文词宗主孟学士所作文章就像腾起的蛟龙、飞舞的彩凤;王将军的兵器库中,藏有像紫电、青霜这样锋利的宝剑。由于父亲在交趾做县令,我在探亲途中经过这个著名的地方。我年幼无知,竟有幸亲身参加了这次盛大的宴会。
    九月,三秋潦水尽而寒潭清,烟光凝而暮山紫。骖騑于上路,访风景于崇阿。临帝子长洲,得天人之旧馆。层峦耸翠,上出重霄;飞阁流丹,下临无地。鹤汀凫渚,穷岛屿之萦回;桂殿兰宫,即冈峦之体势。
    正当深秋九月之时,雨后的积水消尽,寒凉的潭水清澈,天空凝结着淡淡的云烟,暮霭中山峦呈现一片紫色。在高高的山路上驾着马车,在崇山峻岭中访求风景。来到昔日帝子的长洲,找到仙人居住过的宫殿。这里山峦重叠,青翠的山峰耸入云霄。凌空的楼阁,红色的阁道犹如飞翔在天空,从阁上看不到地面。仙鹤野鸭栖止的水边平地和水中小洲,极尽岛屿的纡曲回环之势;华丽威严的宫殿,依凭起伏的山峦而建。
    绣闼,俯雕甍,山原旷其盈视,川泽纡其骇瞩闾阎地,钟鸣鼎食之家;舸舰弥津,青雀黄龙。云雨霁,彩彻明。霞与孤鹜齐飞,秋水共长天一色。渔舟唱晚,响穷彭蠡之滨,雁阵惊寒,声断衡阳之浦。
    推开雕花精美的阁门,俯视彩饰的屋脊,山峰平原尽收眼底,河流迂回的令人惊讶。遍地是里巷宅舍,许多钟鸣鼎食的富贵人家。舸舰塞满了渡口,尽是雕上了青雀黄龙花纹的大船。云消雨停,阳光普照,天空晴朗;落日映射下的彩霞与孤独的野鸭一齐飞翔,秋天的江水和辽阔的天空连成一片,浑然一色。傍晚时分,渔夫在渔船上歌唱,那歌声响彻彭蠡湖滨;深秋时节,雁群感到寒意而发出惊叫,哀鸣声一直持续到衡阳的水滨。
    遥襟甫畅,逸兴飞。爽籁发而清风生,纤歌凝而白云遏睢园绿竹,气凌彭泽之樽;邺水朱华光照临川之笔四美具,二难并。穷睇眄于中天,极娱游于暇日。天高地,觉宇宙之无穷;兴尽悲来,识盈虚之有数。望长安于日下,目吴会于云间。地势极而南溟深,天柱高而北辰远。关山难越,谁悲失路之人;萍水相逢,尽是他乡之客。怀帝阍而不见,奉宣室以何年?
    放眼远望,胸襟顿时感到舒畅,超逸的兴致立即兴起。排箫的音响引来徐徐清风,柔缓的歌声吸引住飘动的白云。今日盛宴好比当年梁园雅集,大家酒量也胜过陶渊明。参加宴会的文人学士,就像当年的曹植,写出“朱华冒绿池”一般的美丽诗句,其风流文采映照着谢灵运的诗笔。音乐与饮食、文章和言语这四种美好的事物都已经齐备,良辰美景、赏心乐事这两个难得的条件也凑合在一起了。向天空中极目远眺,在假日里尽情欢娱。苍天高远,大地寥廓,令人感到宇宙的无穷无尽。欢乐逝去,悲哀袭来,意识到万事万物的的消长兴衰是有定数的。远望长安沉落到夕阳之下,遥看吴郡隐现在云雾之间。地理形势极为偏远,南方大海特别幽深,昆仑山上天柱高耸,缈缈夜空北极远悬。关山重重难以越过,有谁同情我这不得志的人?偶然相逢,满座都是他乡的客人。怀念着君王的宫门,但却不被召见,什么时候才能像贾谊那样到宣室侍奉君王呢?
    嗟乎!时运不齐,命途多舛。冯唐易老李广难封屈贾谊于长沙,非无圣主;窜梁鸿于海曲,岂乏明时?所赖君子见机,达人知命。老当益壮,宁移白首之心?穷且益坚,不坠青云之志。酌贪泉而觉爽,处涸辙以犹欢。北海虽赊,扶摇可接;东隅已逝桑榆非晚。孟尝高洁,空余报国之情;阮籍猖狂,岂效穷途之哭!
    呵,各人的时机不同,人生的命运多有不顺。冯唐容易衰老,李广立功无数却难得封侯。使贾谊这样有才华的人屈居于长沙,并不是当时没有圣明的君主,使梁鸿逃匿到齐鲁海滨,不是在政治昌明的时代吗?只不过由于君子安于贫贱,通达的人知道自己的命运罢了。年岁虽老而心犹壮,怎能在白头时改变心情?遭遇穷困而意志更加坚定,在任何情况下也不放弃自己的凌云之志。即使喝了贪泉的水,心境依然清爽廉洁;即使身处于干涸的车辙中,胸怀依然开朗愉快。北海虽然遥远,乘着大风仍然可以到达;晨光虽已逝去,珍惜黄昏却为时不晚。孟尝君心性高洁,但白白地怀抱着报国的热情,阮籍为人放纵不羁,我们怎能学他那种走到穷途的就哭泣的行为呢!
    勃,三尺微命,一介书生。无路请缨,等终军之弱冠;有怀投笔,慕宗悫之长风。舍簪笏于百龄,奉晨昏于万里。非谢家之宝树接孟氏之芳邻。他日趋庭,叨陪对;今兹捧袂,喜托龙门。杨意不逢,抚凌云而自惜;钟期既遇,奏流水以何惭?
    我地位卑微,只是一介书生。虽然和终军年龄相等,却没有报国的机会。像班超那样有投笔从戎的豪情,也有宗悫“乘风破浪”的壮志。如今我抛弃了一生的功名,不远万里去朝夕侍奉父亲。虽然不是谢玄那样的人才,但也和许多贤德之士相交往。过些日子,我将到父亲身边,一定要像孔鲤那样接受父亲的教诲;而今天我能谒见阎公受到接待,高兴得如同登上龙门一样。假如碰不上杨得意那样引荐的人,就只有抚拍着自己的文章而自我叹惜。既然已经遇到了钟子期,就弹奏一曲《流水》又有什么羞愧呢?
    呜乎!胜地不常,盛筵难再;兰亭已矣,梓泽丘墟。临别赠言,幸承恩于伟饯;登高作赋,是所望于群公。敢竭鄙怀,恭疏短引一言均赋四韵俱成请洒潘江,各倾陆海云尔:
    呵!名胜之地不能常存,盛大的宴会难以再逢。兰亭集会的盛况已成陈迹,石崇的梓泽也变成了废墟。承蒙这个宴会的恩赐,让我临别时作了这一篇序文,至于登高作赋,这只有指望在座诸公了。我只是冒昧地尽我微薄的心意,作了短短的引言。我的一首四韵小诗也已写成。请各位像潘岳、陆机那样,展现江海般的文才吧!
    滕王高阁临江渚,佩玉鸣鸾罢歌舞。
    画栋朝飞南浦云,珠帘暮卷西山雨。
    闲云潭影日悠悠,物换星移几度秋。
    阁中帝子今何在?槛外长江空自流。
    
  2. 无意中找到之前漏掉的两首小诗
    铜鼓景色媲霓裳,
    风光旖旎现牛羊。
    鬼斧神工为谁造?
    瑶池仙境王母娘。
    
    看大连同学赏秋拜佛观鱼视频有感
    
    赏秋拜佛横山寺,
    一曲钢琴寄秋思。
    关外水乡数大连,
    人间天堂有福地。 
    

十一月十八日 等待变化等待机会

  1. 红叶添秋色,
    风凝白云遏。
    五彩斑斓景,
    行人心底乐。
    

十一月十九日 等待变化等待机会

  1. 相比于春色,
    秋景更让人痴醉。
    
    春天是充满幻想的季节,
    给人无数的憧憬。
    而秋天是收获的季节,
    不仅是简单的呈现在眼前的果实,
    更有夏日成熟之后的余温。
    
    春天虽然刚刚脱离了寒冬的磨难而充满生机却也不无残留着过多的料峭。
    秋天虽然明知道这是冬风来临前最后的灿烂却不易余地的展现所有的斑斓。
    

十一月三十日 等待变化等待机会

  1. 风闻某些敏感事态感觉某种不好的趋势。
    薄裘不抵霜晨寒,
    冬日微暖露未干。
    心忧万里家国事,
    上下不一左右难。 
    

十二月四日 等待变化等待机会

  1. 《清平乐》
    
    夜雨绵绵,
    扑面不觉寒。
    四海漂泊又一年,
    空寂更添思念。
    
    慨叹岁月匆匆,
    少年豪气无踪。
    纵然痛心疾首,
    对外装作从容。 
    
  2. 《清平乐》
    
    功名看淡,
    心中无羁绊。
    人生不过宴席散,
    征途早就过半。
    
    盛世浮华成风,
    回首才知是空。
    心中稍有悲痛,
    转身还算从容。
    
  3. 夜雨绵绵,
    扑面不觉寒。
    四海漂泊又一年,
    空寂更添思念。
    
    庸才最爱成功,
    志大本领稀松。
    造化总是作弄,
    镜花水月是空。 
    
  4. 月
    
    冬夜月不全,
    月下独盘桓。
    心恐云遮月,
    大声唤月还。
    
  5. 凭栏远眺添乡愁,
    冬雨无心落不休。
    洗净浮尘留干骨,
    一根深埋土中留。
    

十二月九日 等待变化等待机会

  1. 秋风
    
    秋风本无情,
    扑面惊我心。
    挥手随风去,
    心意不能平。
    
  2. 看诗友煮诗酿字待雪诗有感
    
    酿字为酒耍浪漫,
    蓝天不予诗人面。
    莫如执笔写春秋,
    无雪残冬也斑斓。
    

十二月十二日 等待变化等待机会

  1. 这是我在人工智能基础上改写的
    
    冬夜冬月寒,
    枯树孤影单。
    西风呼呼吹,
    荒草潇潇残。
    万里长空默,
    邻村锣鼓喧。
    此情此景中,
    人机在线谈。
    

十二月十三日 等待变化等待机会

  1. 寒风凛冽夕阳沉,
    枯草凋零落无声。
    小鸟枯枝栖寂寞,
    冷眼凭栏观浮尘。
    寒意深深几傍晚,
    残阳斜斜正黄昏。
    时光流逝芳何再,
    凄凉冬日灰蒙蒙。 
    
  2. 清晨染绿山,
    芳草望无边。
    清风吹拂水,
    涟漪碧波翻。
    日落绚烂处,
    残阳挂林间。
    牧歌声声慢,
    余音袅袅绵。
    
  3. 冬日夕阳斜,
    寒鸦枯枝歇。
    向晚寒意盛,
    小径行人绝。 
    

十二月十八日 等待变化等待机会

  1. 	
    在人工智能启发下拼凑的诗
    
    白衣轻翎舞清溪,
    绿柳纤袖动碧丝。
    青丝鬟卷摄心意,
    芳躯馥郁沁人脾。
    朱唇艳艳玉面雪,
    秀眉峰峰皓齿皙。
    娉婷身姿世间罕,
    薰麝芳馨凡世稀。
    瑶碧华茵覆朝霞,
    玉腕花环披罗衣。
    纤指微动荷花绽,
    浅唱低吟百鸟啼。
    踟蹰窈步杨花伴,
    玉足轻踏细柳依。
    秀颜玉貌铅华出,
    袅娜鲜妍美不辞。 
    
  2. 《清平乐》--抗疫情最后冲刺
    云淡天蓝,
    阳光更绚烂。
    解禁反而怕危险,
    哪有绝对安全?
    最忌盲目跟风,
    遇到困难别怂。
    绝不自缚手脚,
    闯关夺隘靠冲。
    

十二月三十一日 等待变化等待机会

  1. 《浪淘沙令》
    
    普京实在难,
    千里战线。
    俄军深陷乌克兰。
    嘴里空喊核威慑,
    手上缺钱。
    
    遥想前苏联
    在阿富汗。
    一仗打了整十年。
    结果帝国崩溃也,
    三十年前。
    
  2. 夜已深沉心事多,
    躁动心情向谁说。
    思绪脱缰渐狂野,
    顿悟记忆只剩我。
    
  3. 《浪淘沙令》
    
    聚会迎新年,
    《品味长安》。
    各色美食数西安。
    忽见白发生前额,
    似水流年。
    
    登陆二十年,
    历历眼前。
    人生轨迹交叉点。
    纵然相逢也是客,
    回首枉然。
    
    注:品味长安为一家在温哥华海边的西安风味餐馆。

Smiley face