0%

從one_of說起

之前寫過這樣的 Code Snippet

1
2
3
4
5
6
7
8
9
10
#include <initializer_list>
enum state_type { IDLE, CONNECTING, CONNECTED, DISCONNECTING, DISCONNECTED };
template <typename ...Ts>
bool is_any_of(state_type s, const Ts& ...ts)
{
bool match = false;
(void)std::initializer_list<int>{(match |= (s == ts), 0)...};
return match;
}
is_any_of(s, IDLE, DISCONNECTING);

這程式碼適用於C++11,如果用C++17可以用fold expression更進一步簡化

1
2
3
4
5
6
enum state_type { IDLE, CONNECTING, CONNECTED, DISCONNECTING, DISCONNECTED };
template <typename ...Ts>
bool is_any_of(state_type s, const Ts& ...ts)
{
return ((s == ts) || ...);
}

能不能更進一步?

variadic non type template

1
2
3
4
5
6
7
enum state_type { IDLE, CONNECTING, CONNECTED, DISCONNECTING, DISCONNECTED };
template <state_type ... states>
bool is_any_of(state_type s)
{
return ((s == states) || ...);
}
is_any_of<IDLE, DISCONNECTING>(s);

可以看出,這個版本將比較值跟貝比教值分開,比原先的版本更容易看出語意,不過問題也很明顯,所以被比較值要在編譯期就決定了,靈活度反而比不上原先的方案,有沒有折衷的方案?

std::tuple

看到Modern Techniques for Keeping Your Code DRY這方案真是眼睛一亮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template <typename ...Ts>
struct any_of : private std::tuple<Ts...> {
using std::tuple<Ts...>::tuple;
template <typename T>
bool operator==(const T& t) const {
return std::apply([&t](const auto& ...ts) { return ((ts == t) | ...); },
static_cast<const std::tuple<Ts...>&>(*this));
}
template <typename T>
friend bool operator==(const T& lh, const any_of& rh) { return rh == lh; }
};

template <typename ...Ts>
any_of(Ts...)->any_of<Ts...>;
any_of(IDLE, DISCONNECTING) == s
s == any_of(IDLE, DISCONNECTING)

用CTAD,std::tuple,std::apply的組合技包裝成威力十足的武器

Add more operation

假設我們現在要加上<的比較

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename ...Ts>
struct any_of : private std::tuple<Ts...> {
using std::tuple<Ts...>::tuple;
template <typename T>
bool operator==(const T& t) const {
return std::apply([&t](const auto& ...ts) { return ((ts == t) || ...); },
static_cast<const std::tuple<Ts...>&>(*this));
}

template <typename T>
bool operator<(const T& t) const {
return std::apply([&t](const auto& ...ts) { return ((ts < t) || ...); },
static_cast<const std::tuple<Ts...>&>(*this));
}
};

可以看出兩段程式碼大同小異,能否亙進一步?

Higher order functions

由於兩個operator都是由一系列or operation組成的
因此我們可以把or operation提出來

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template <typename F, typename ...Ts>
bool or_elements(const F& f, const std::tuple<Ts...>& t) {
return std::apply([&f](const auto& ...ts) { return (f(ts) || ...); }, t);
}

template <typename ...Ts>
struct any_of : private std::tuple<Ts...> {
using std::tuple<Ts...>::tuple;
template <typename T>
bool operator==(const T& t) const {
return or_elements([&t](const auto& v) { return v == t; }, *this);
}

template <typename T>
bool operator<(const T& t) const {
return or_elements([&t](const auto& v) { return v < t; }, *this);
}
};

template <typename ...Ts>
any_of(Ts...)->any_of<Ts...>;

至此any_of的東西就差不多了,那麼來寫all_of吧

all_of implementation

大同小異

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template <typename F, typename ...Ts>
bool and_elements(const F& f, const std::tuple<Ts...>& t) {
return std::apply([&f](const auto& ...ts) { return (f(ts) && ...); }, t);
}

template <typename ...Ts>
struct all_of : private std::tuple<Ts...> {
using std::tuple<Ts...>::tuple;
template <typename T>
bool operator==(const T& t) const {
return and_elements([&t](const auto& v) { return v == t; }, *this);
}

template <typename T>
bool operator<(const T& t) const {
return and_elements([&t](const auto& v) { return v < t; }, *this);
}
};

template <typename ...Ts>
all_of(Ts...)->all_of<Ts...>;

一般來說做到此就行了,能否亙進一步?

Inheritance

再上去就是炫技了,實用度就不高了,編譯時間又拉長不少

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
struct or_elements {
template <typename F, typename ...Ts>
static bool apply(const F& f, const std::tuple<Ts...>& t) {
return std::apply([&f](const auto& ...ts) { return (f(ts) || ...); }, t);
}
};

struct and_elements {
template <typename F, typename ...Ts>
static bool apply(const F& f, const std::tuple<Ts...>& t) {
return std::apply([&f](const auto& ...ts) { return (f(ts) && ...); }, t);
}
};

template <typename Op, typename ...Ts>
struct op_t : private std::tuple<Ts...> {
using std::tuple<Ts...>::tuple;
template <typename T>
bool operator==(const T& t) const {
return Op::apply([&t](const auto& v) { return v == t; }, *this);
}

template <typename T>
bool operator<(const T& t) const {
return Op::apply([&t](const auto& v) { return v < t; }, *this);
}

template <typename T>
friend bool operator==(const T& lh, const op_t& rh) { return rh == lh; }
template <typename T>
friend bool operator>(const T& lh, const op_t& rh) { return rh < lh; }
};

template <typename ...Ts>
struct any_of : op_t<or_elements, Ts...> {
using op_t<or_elements, Ts...>::op_t;
};

template <typename ...Ts>
struct all_of : op_t<and_elements, Ts...> {
using op_t<and_elements, Ts...>::op_t;
};

template <typename ...Ts>
any_of(Ts...)->any_of<Ts...>;
template <typename ...Ts>
all_of(Ts...)->all_of<Ts...>;

Reference

Modern Techniques for Keeping Your Code DRY
Lambdas: From C++11 to C++20, Part 1
Lambdas: From C++11 to C++20, Part 2

從C# Extension Method說起

1
2
3
4
5
6
7
8
9
static class StringUtilities
{
public static int WordCount(this string text)
{
return text.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Length;
}
}
var text = "This is an example";
var count = text.WordCount();

將一個Method綁在已有的type之上
在C++有類似的提案,叫做UFCS
不過提案會不會過還不知道,但是可以用目前的技術模擬
以下從Reddit看來的兩種方式,寫下自己的看法

Ver 1

使用CRTP技巧來達成

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
// One way to emulate UFCS in standard C++.

// A CRTP class that forwards .call<F>(...) to F::function(*this, ...)
template<typename T>
struct uniform_call_syntax {
template <typename F, typename... Args>
constexpr auto call(Args... args) {
return F::function(static_cast<T&>(*this), args...);
}
};

// Usage:
struct my_type : public uniform_call_syntax<my_type> {
int value;
};

struct set_value {
static my_type& function(my_type& o, int i) {
o.value = i;
return o;
}
};

struct add_value {
static my_type& function(my_type& o, int i) {
o.value += i;
return o;
}
};

struct get_value {
static int function(const my_type& o) {
return o.value;
}
};

int test() {
my_type obj;

return obj
.call<set_value>(10)
.call<add_value>(20)
.call<get_value>(); // returns 30.

}

作法顯而易見,每個需要UFCS功能的都需要繼承uniform_call_syntax,然後每個Externsion method都需要有同樣的function name
缺點也顯而易見,要有這功能就需要繼承,有些type是不允許修改的

Ver 2

版本二需要C++14以上的標準

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
#include<string>
#include<iostream>

// Same as Haskell's infix operator `&` or F#'s operator `|>`
template<typename Arg, typename F>
decltype(auto) operator % (Arg&& arg, F&& fn)
{
return std::forward<F>(fn)(std::forward<Arg>(arg));
}

namespace my
{
auto tolower = []
{
return [](auto&& s) -> decltype(auto)
{
for (auto& c : s) {
c = ('A' <= c && c <= 'Z') ? c - ('Z' - 'z') : c;
}
return decltype(s)(s);
};
};

auto append(const std::string& what)
{
return [&](auto&& s) -> decltype(auto)
{
s += what;
std::cerr << "is-lvalue-reference:" << std::is_lvalue_reference<decltype(s)>::value << ";" << s << "\n";
return std::forward<decltype(s)>(s);
};
};
}

int main()
{
// as rvalue-references
auto s1 = std::string{ "HELLO," }
% my::tolower()
% my::append(" world");

// as lvalue-references
auto s2 = std::string{ "GOODOBYE," };
s2% my::tolower()
% my::append(" cruel world.");

std::cerr << s1 << "\n" << s2 << "\n";

return 0;
}

看起來很美,很像Functional Programming的Pipe觀念
不過對Programmer的要求比較高,至少要有FP的基本觀念才比較容易理解

看了C++17的文章之後,還是搞不太清楚兩者的差異,於是自己寫程式驗證一下

Capture this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <memory>
struct Obj {
Obj(int value) : v(std::make_unique<int>(value)) {}
~Obj() { printf("Obj have been destroyed\n"); }
Obj(const Obj& o) {
printf("Copy constructor invoked\n");
v = std::make_unique<int>(*o.v);
}
Obj(Obj&& o) {
printf("Move constructor invoke\n");
v = std::move(o.v);
}
auto test() { return [this] { printf("%d\n", *v); }; }
std::unique_ptr<int> v;

};
int main()
{
auto test = Obj(123).test();
test();
}

很不意外的,Crash掉了
如果我們把呼叫改成Obj(123).test()(),Obj的Lifetime就跟執行時間一樣長了,可以正常執行

Capture *this

1
2
3
struct Obj {
auto test() { return [*this] { printf("%d\n", *v); }; }
};

跟上面一樣,只是修改了capture this的方式

1
2
3
4
Copy constructor invoked
Obj have been destroyed
123
Obj have been destroyed

由此可見他會呼叫Copy construtor,然後摧毀了原先的Obj,lambda中的this指的就是Copy的Obj

Simulate *this without C++17

要這麼做也不是不行

1
2
3
struct Obj {
auto test() { return [self = *this] { printf("%d\n", *self.v); }; }
};

C++17的程式碼比較乾淨,不過這個版本就很清楚知道這個this是複製品了

最近實在是太頹廢了,強迫自己寫些新東西
蟒蛇有些新玩具,可以加入自己的工具組合之中

PySnooper

Github Project主業
首先先安裝pysnooper這個package

1
$ pip install pysnooper

然後來個最簡單的示範,寫個比官方範例更複雜一點

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@pysnooper.snoop()
def findPrimes(number):
num = 2
primes = []
while num <= number:
div = 2
flag = True
while div * div <= num and flag:
if num % div == 0:
flag = False
div = div + 1
if flag:
primes.insert(len(primes), num)
num = num + 1
return primes
print(findPrimes(10))

然後可以列出所有中間產物的過程

輸出到Log file

可以指定到不同的Log file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import pysnooper

@pysnooper.snoop("./debug1.log")
def isPrime(num):
div = 2
while div * div <= num:
if num % div == 0:
return False
div = div + 1
return True

@pysnooper.snoop("./debug.log")
def findPrimes(number):
num = 2
primes = []
while num <= number:
if isPrime(num):
primes.insert(len(primes), num)
num = num + 1
return primes
print(findPrimes(10))

用不同的Prefix區分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import pysnooper

@pysnooper.snoop("./debug.log", prefix="--isPrime--")
def isPrime(num):
div = 2
while div * div <= num:
if num % div == 0:
return False
div = div + 1
return True

@pysnooper.snoop("./debug.log", prefix="--findPrimes--")
def findPrimes(number):
num = 2
primes = []
while num <= number:
if isPrime(num):
primes.insert(len(primes), num)
num = num + 1
return primes
print(findPrimes(10))

stackprinter

Github Project主業
要做的還是先裝上去

1
$ pip install stackprinter

接著我們修改上面的範例,讓它Crash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def isPrime(num):
div = 0
while div * div <= num:
if num % div == 0:
return False
div = div + 1
return True

def findPrimes(number):
num = 2
primes = []
while num <= number:
if isPrime(num):
primes.insert(len(primes), num)
num = num + 1
return primes
print(findPrimes(10))

執行之後產生以下的 Crash info

1
2
3
4
5
6
7
8
Traceback (most recent call last):
File "a.py", line 20, in <module>
print(findPrimes(10))
File "a.py", line 16, in findPrimes
if isPrime(num):
File "a.py", line 7, in isPrime
if num % div == 0:
ZeroDivisionError: integer division or modulo by zero

如果我們加上了stachpointer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import stackprinter
stackprinter.set_excepthook(style='darkbg2')

def isPrime(num):
div = 0
while div * div <= num:
if num % div == 0:
return False
div = div + 1
return True

def findPrimes(number):
num = 2
primes = []
while num <= number:
if isPrime(num):
primes.insert(len(primes), num)
num = num + 1
return primes
print(findPrimes(10))

會變成這樣子

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
File a.py, line 20, in <module>
16 if isPrime(num):
17 primes.insert(len(primes), num)
18 num = num + 1
19 return primes
--> 20 print(findPrimes(10))

File a.py, line 16, in findPrimes
12 def findPrimes(number):
13 num = 2
14 primes = []
15 while num <= number:
--> 16 if isPrime(num):
17 primes.insert(len(primes), num)
..................................................
number = 10
num = 2
primes = []
..................................................

File a.py, line 7, in isPrime
4 def isPrime(num):
5 div = 0
6 while div * div <= num:
--> 7 if num % div == 0:
8 return False
..................................................
num = 2
div = 0
..................................................

ZeroDivisionError: integer division or modulo by zero

有顏色和箭頭比較容易分析哪裡出錯

既然C++20把Coroutine列為標準配備之後,必須試著了解玩法
從最簡單的範例開始

The simplest coroutine example

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
#include <iostream>
#include <experimental/coroutine>

#define CORETURN_ENABLE

struct generator {
struct promise_type {
int current_value;
auto initial_suspend() { return std::experimental::suspend_always{}; }
auto final_suspend() { return std::experimental::suspend_always{}; }
auto get_return_object() { return generator{handle_type::from_promise(*this)}; }
void unhandled_exception() { std::exit(1); }
auto yield_value(int v) {
current_value = v;
return std::experimental::suspend_always{};
}
#ifdef CORETURN_ENABLE
void return_value(int v) {
current_value = v;
}
#else
void return_void() {}
#endif
};
bool move_next() {
coro.resume();
return !coro.done();
}
int current_value() { return coro.promise().current_value; }
using handle_type = std::experimental::coroutine_handle<promise_type>;
handle_type coro;
};
generator count()
{
co_yield 42;
co_yield 56;
#ifdef CORETURN_ENABLE
co_return 999;
#endif
}

int main() {
auto g = count();
while (g.move_next())
std::cout << g.current_value() << std::endl;
#ifdef CORETURN_ENABLE
std::cout << g.current_value() << std::endl;
#endif
return 0;
}

分析

假設我們有一個Coroutine的程式碼片段,例如

1
2
3
4
5
template <typename TRet, typename … TArgs>
TRet func(TArgs args…)
{
body;
}

邊義氣會幫我們做類似這樣的處理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template <typename TRet, typename ... TArgs>
TRet func(TArgs args...)
{
using promise_t = typename coroutine_traits<TRet, TArgs...>::promise_type;

promise_t promise;
auto __return__ = promise.get_return_object();

co_await promise.initial_suspend();

try
{ // co_return expr; => promise.return_value(expr); goto final_suspend;
body; // co_return; => promise.return_void(); goto final_suspend;
} // co_yield expr; => co_await promise.yield_value(expr);
catch (...)
{
promise.set_exception(std::current_exception());
}

final_suspend:
co_await promise.final_suspend();
}

對照Pseudo code func和上面範例的countˇ以及promise_tgenerator::promise_type明白了一些東西

– 任何包含co_yieldco_return或者co_await的程式碼都是corutine block,必須定義一個structure,而這個structure必須有個promise_type
– promeise_type需要六個函數, return_valuereturn_void不可同時存在
– 中間狀態必須保存在promise_type中
– 當co_yield交出控制權之後,必須使用coro.resume()恢復執行

以下討論另外一個關鍵字 co_await

co_await

先從古早時期的Callback說起,給出一個範例

From a callback start

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <thread>
#include <iostream>
using call_back = std::function<void(int)>;
void Add100ByCallback(int init, call_back f)
{
std::thread t([init, f]() {
std::this_thread::sleep_for(std::chrono::seconds(1));
f(init + 100);
});
t.detach();
}
int main()
{
Add100ByCallback(10, [](int v) {
std::cout << v << "\n";
});
std::this_thread::sleep_for(std::chrono::seconds(5));
}

程式本身沒有什麼意義,只是模擬長時間處理的情形

Awaitable object

Coroutine版根據上面的版本修改,新增了一些東西

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
struct Task
{
struct promise_type {
auto get_return_object() { return Task{}; }
auto initial_suspend() { return std::experimental::suspend_never{}; }
auto final_suspend() { return std::experimental::suspend_never{}; }
void unhandled_exception() { std::terminate(); }
void return_void() {}
};
};

struct Add100AWaitable
{
Add100AWaitable(int init) :init_(init) {}
bool await_ready() const { return false; }
int await_resume() { return result_; }
void await_suspend(std::experimental::coroutine_handle<> handle)
{
auto f = [handle, this](int value) mutable {
result_ = value;
handle.resume();
};
Add100ByCallback(init_, f);
}
int init_;
int result_;
};

Task Add100ByCoroutine(int init, call_back f)
{
std::cout << "Before co_await: " <<std::this_thread::get_id() << "\n";
int ret = co_await Add100AWaitable(init);
std::cout << "After co_await: " << std::this_thread::get_id() << "\n";
f(ret);
}

上面的Task不需要多做介紹,Add100ByCoroutine也不需要多說些什麼
直接看Add100AWaitable的部分
同樣的Add100AWaitable也有必須要注意的點
– 中間值一樣存在 Awaitable object
– 必須有await_readyawait_resumeawait_suspend的存在
– 以上面的例子來看,當Callback完成之後,我們手動讓Coroutine繼續執行上去
– 呼叫Add100ByCoroutine和corutime resume的Thread不一定是同一個,需要把狀態保留到awaitable object中

Reference

How C++ coroutines work
Coroutine Theory
From Algorithms to Coroutines in C++
Coroutines and Reference Parameters

Range-V3要進C++20 Standard,看了一下他給的範例

Pythagorean triple如何印出前十組Pythagorean triple

從最簡單的方案開始看

Direct Method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

int main() {
int i = 0;
for (int z = 1; true; ++z) {
for (int x = 1; x < z; ++x) {
for (int y = x; y < z; ++y) {
if (x * x + y * y == z * z) {
printf("(%d,%d,%d)\n", x, y, z);
if (++i == 10) goto done;
}
}
}
}
done:;
}

不會很難理解,必須迴圈外部維護一個狀態,當狀態滿足之後強行離開迴圈

Callback Solution

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
#include <iostream>
#include <tuple>
#include <type_traits>

template<class F>
auto boolify(F& f) {
return [&](auto&&... args) {
using R = decltype(f(std::forward<decltype(args)>(args)...));
if constexpr (std::is_void_v<R>) {
f(std::forward<decltype(args)>(args)...);
return false;
}
else {
return f(std::forward<decltype(args)>(args)...);
}
};
}

template<class F>
void generate_triples(F f) {
for (int z = 1; true; ++z) {
for (int x = 1; x < z; ++x) {
for (int y = x; y < z; ++y) {
if (x*x + y * y == z * z) {
bool stop = boolify(f)(std::make_tuple(x, y, z));
if (stop) return;
}
}
}
}
}

template<class F>
auto take(int k, F f) {
return [f, i = k](auto x) mutable -> bool {
return (i-- == 0) || boolify(f)(x);
};
}

int main() {
generate_triples(
take(10,
[&](auto triple) {
std::cout << '('
<< std::get<0>(triple) << ','
<< std::get<1>(triple) << ','
<< std::get<2>(triple) << ')' << '\n';
}
)
);
}

這個難看懂很多
首先程式主體

1
2
3
4
5
6
7
8
9
10
11
12
13
template<class F>
void generate_triples(F f) {
for (int z = 1; true; ++z) {
for (int x = 1; x < z; ++x) {
for (int y = x; y < z; ++y) {
if (x*x + y * y == z * z) {
bool stop = boolify(f)(std::make_tuple(x, y, z));
if (stop) return;
}
}
}
}
}

是沒壁的,我們的中止條件就在 take(10, ...)這個函數之間
一旦滿足條件,整個genertat4e_tyiples就結束了
我們看看 take的實作

1
2
3
4
5
6
template<class F>
auto take(int k, F f) {
return [f, i = k](auto x) mutable -> bool {
return (i-- == 0) || boolify(f)(x);
};
}

我們把state包含在lambda之中,避免被外界汙染
至於boolify這個函數不重要,不影響分析

這個版本比起上面那個,邏輯切個更清晰
不過複雜度也跟著增加了

Coroutine Solution

隨著C++20加入stackless coroutine,這個問題也可以用coroutine來解

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
#include <iostream>
#include <tuple>
#include <experimental/coroutine>

template<class T>
struct generator {
struct promise_type;
using handle = std::experimental::coroutine_handle<promise_type>;
struct promise_type {
T current_value;
auto get_return_object() { return generator{ handle::from_promise(*this) }; }
auto initial_suspend() { return std::experimental::suspend_always{}; }
auto final_suspend() { return std::experimental::suspend_always{}; }
void unhandled_exception() { std::terminate(); }
void return_void() {}
auto yield_value(T value) {
current_value = value;
return std::experimental::suspend_always{};
}
};
bool move_next() { return coro ? (coro.resume(), !coro.done()) : false; }
T current_value() { return coro.promise().current_value; }
generator(generator const&) = delete;
generator(generator && rhs) : coro(rhs.coro) { rhs.coro = nullptr; }
~generator() { if (coro) coro.destroy(); }
private:
generator(handle h) : coro(h) {}
handle coro;
};

auto triples() -> generator<std::tuple<int, int, int>> {
for (int z = 1; true; ++z) {
for (int x = 1; x < z; ++x) {
for (int y = x; y < z; ++y) {
if (x*x + y * y == z * z) {
co_yield std::make_tuple(x, y, z);
}
}
}
}
}

int main() {
auto g = triples();
for (int i = 0; i < 10; ++i) {
g.move_next();
auto triple = g.current_value();
std::cout << '('
<< std::get<0>(triple) << ','
<< std::get<1>(triple) << ','
<< std::get<2>(triple) << ')' << '\n';
}
}

由於coroutine還是新玩意,目前對他的掌握度不適很高,這個方案僅供參考

Range-V3 Solution

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
// A sample standard C++20 program that prints
// the first N Pythagorean triples.
#include <iostream>
#include <optional>
#include <range/v3/all.hpp>

using std::get;
using std::optional;
using std::make_tuple;
using std::cout;
using ranges::view_interface;
using ranges::iterator_t;
namespace view = ranges::v3::view;

// maybe_view defines a view over zero or one
// objects.
template<class T>
struct maybe_view : view_interface<maybe_view<T>> {
maybe_view() = default;
maybe_view(T t) : data_(std::move(t)) {
}
T const *begin() const noexcept {
return data_ ? &*data_ : nullptr;
}
T const *end() const noexcept {
return data_ ? &*data_ + 1 : nullptr;
}
private:
optional<T> data_{};
};

// "for_each" creates a new view by applying a
// transformation to each element in an input
// range, and flattening the resulting range of
// ranges.
// (This uses one syntax for constrained lambdas
// in C++20.)
inline constexpr auto for_each =
[](auto&& r, auto fun) {
return decltype(r)(r)
| view::transform(std::move(fun))
| view::join;
};

// "yield_if" takes a bool and a value and
// returns a view of zero or one elements.
inline constexpr auto yield_if =
[](bool b, auto x) {
return b ? maybe_view{std::move(x)}
: maybe_view<decltype(x)>{};
};

int main() {
// Define an infinite range of all the
// Pythagorean triples:
using view::iota;
auto triples =
for_each(iota(1), [](int z) {
return for_each(iota(1, z+1), [=](int x) {
return for_each(iota(x, z+1), [=](int y) {
return yield_if(x*x + y*y == z*z,
make_tuple(x, y, z));
});
});
});

// Display the first 10 triples
for(auto triple : triples | view::take(10)) {
cout << '('
<< get<0>(triple) << ','
<< get<1>(triple) << ','
<< get<2>(triple) << ')' << '\n';
}
}

看起來還不錯,不過編譯時間嚇死人…
目前還是先考慮方案一或二吧,三和四等掌握度高一點再說

這需求有點奇怪,不過這倒是我想要的編譯流程
先從Golang來說吧,1.11前的版本只會把dependency module都裝入GOPATH中,就算有godep之類的東西也覺得不好用

由於自己是Developer,需要分析Module間的互動,甚至改改程式碼,分析一下,放在GOPATH不是不好,只是我想要以下的需求

– 修改Dependcny Module的程式碼,由於牽一髮動全身,改動之後可能造成其他相依於此Module的全部掛點
– 希望放在Repo附近,而不是在GOPATH底下尋找

Cargo跟GoLang的行為模式差不多,Module放在.cargo底下
不利於開發研究

於是找出一套屬於自己的方式了

Golang

首先,先安裝到Golang 1.11以上
go-fastdfs舉例,他缺少go.mod,於是我們自行補上

1
2
3
4
5
6
7
8
9
10
11
$ git clone https://github.com/sjqzhang/go-fastdfs
$ cd go-fastdfs
$ rm vendor -r # 刪除掉原先的vendor目錄
$ go mod init
go: creating new go.mod: module github.com/sjqzhang/go-fastdfs
$ go mod vendor # 這下Dependcy Module都在vendor底下了
$ go build -mod=vendor # 產生go-fastdfs了
# 接著在修改vendor/github.com/astaxie/beego/httplib/httplib.go
# 使其編譯不過
$ go build -mod=vendor
./fileserver.go:264:13: undefined: httplib.BeegoHTTPSettings

看來真的是用vendor目錄下的Module下去編譯

Rust

Cargo的部分麻煩一點
首先先安裝cargo-vendor

1
$ cargo install cargo-vendor

yamux來舉例

1
2
3
4
$ git clone https://github.com/paritytech/yamux
$ cd yamux
$ cargo vendor > ~/.cargo/config
$ cargo build

如果要修改vendor裡面的東西比較麻煩
假設我們修改vendor/tokio-io/src/framed_read.rs
下build之後可能出現

1
2
3
4
5
6
$ cargo build
error: the listed checksum of `/yamux/vendor/tokio-io/src/framed_read.rs` has changed:
expected: 07e36ff58fe30bf7b0aa28a998b0bc4e2f78acd04cc1570a877b4eeac1cadcf7
actual: d10d30e1bc1f1d1abc7c28db97fd37ee374d029329aaa78df328bb076163363d

directory sources are not intended to be edited, if modifications are required then it is recommended that [replace] is used with a forked copy of the source

由於我們改了檔案,checksum算出來就不同了
簡易的方案就是在root dir的Cargo.tmol上加上

1
2
[replace]
"tokio-io:0.1.11" = { path = './vendor/tokio-io' }

注意版本號要跟vendor的目錄相同,不然無法編譯

MP4 Muxer的作法,網路上可以找到上百篇,不過On the fly的不多,只好自己動手做了
在這邊走了不少冤枉路,寫起來免得忘記
目標寫一個Library,從Raw H264 Stream和Raw G711 Stream中,打包變成MP4的故事

Libavformat / Libavcodec

大部分多媒體的問題,絕對少不了跟ffmpeg打交道的經驗,所以也在社面花了一點時間

Raw H264 to MP4

由於我們不是走正規路線使用,Google了一下找到Stackoverflow的這篇,沒有什麼問題,不過AV Sync又是另外一個問題,之後再談

G711 to AAC

這點實在搞得我很頭大啊
G711 to PCM這段其實還好,相關的程式碼 這裡
問題在於PCM轉AAC,其中有兩個困難處

– G711的Sample Format是AV_SAMPLE_FMT_S16,不考慮外接AAC Enocder的話,FFmpeg AAC Encoder的Sample Format是AV_SAMPLE_FMT_FLTP,所以需要透過Resampling轉換

– 轉換過的Sample樹不一定跟原先取樣數相同,所以還需要放到Audio Fifo Buffer,等到數目夠的才處理

大致流程可以參考FFMmpeg的範例,一整個麻煩

AV Sync

好不容易通過前兩關,卻死在第三關,也是照FFMpeg的範例來處理PTS的問題,結果怎麼調都失敗,果斷放棄這絲路了,另謀其他做法

libmp4v2 / EasyAACEncoder

不得不大力稱讚EasyAACEncoder這個Project,把G711 to AAC這段難點彌平了不少,不過還是採到坑了
libmp4v2網路上教學不少,就不特別獎了,來說一下遇到那些問題

MP4裡面要放Raw Stream

所以在 PcmToAac.cpp裡面要做如下修改

1
2
/*0 - raw; 1 - ADTS*/
pConfiguration->outputFormat = 0;

看起來都沒問題的部分最後變成夢靨啊..

AV Sync

在上面遇到的問題在這邊也遇到,不過這次找到可行解了,可喜可賀
解法在此

Quicktime issue

路出來的檔案在大部分撥放軟體都沒問題,結果在Quicktime和iOS出問題,Quicktime波一波聲音就消失了,也沒有Open Source可以參考,於是就進入鬼打牆,瞎子摸象的階段
經過多方查證之下,MP4 Info沒什麼問題,不是libmp4v2的問題
試著用ffmpeg轉檔,啜了以下實驗

– 用FFmpeg轉檔,Video / Audio 照舊,QuickTime還是不能播

– 用FFmpeg轉檔,Video照舊,Auido 用FFmpeg AAC Encoder,結果他能撥了

不過我不想走回第一條的路,那太可怕了
不過在ffmpeg轉檔時看到一行

1
Multiple frames in a packet.

這行一直被我忽略掉,因為ffplay沒抱怨,不過走投無路了只好猜是AAC的問題

Revisist EasyAACEncoder

既然懷疑一個Packet裡面有多個Fframe
那可能是

1
Easy_AACEncoder_Encode(handle, pbG726Buffer, gBytesRead, pbAACBuffer, &out_len)

有多個frame吐出來,不過沒有ADTS Header我們無法分辨出FFrame Boundary,於是我們又把ADTS家回去了

1
2
/*0 - raw; 1 - ADTS*/
pConfiguration->outputFormat = 1;

之在外面切割Frame,在一個一個填入MP4,這下Quicktime和iOS就正常了

Conclusion

走了這麼多冤枉路,終於完成一個能動的方案
用FFMpeg方案沒有不好,不過我方面的經驗不夠,之前也沒做太多涉獵
用第二個方案也是遇到了困難,有碰到問題才會知道痛

大家都知道Webpack可以打包許多的Javascript files成一個,方便取用
如果我們的Javascript使用到Webassembly的話可否比照辦理,答案是肯定的
But,我不會用Webpack打包,我也不是專門前端的人,不想花太多時間研究
於是我就改用腦殘版Parcel
零設定就產生了,有興趣的話可以參考demo
接下來的重點是如何放在如何生成Webassembly

AssemblyScript

AssemblyScript
Typescript的subset,會寫前端的話這套適用,不過我是System Language的愛好者

Rust

Rust對Webassembly的支援還真是高啊..只要Cargo.toml中定義成Shared Library
然後下

1
$ cargo build --target wasm32-unknown-unknown  --release

就好了,不過百廢待舉啊

C/C++

基本上有兩個方案
Emscripten 走原先ASM.JS的老路,不過無法直接將生成的wasm放到我的demo上跑
Minimal C/C++ language toolset for building wasm files
有潛力,不過我沒一次成功過,也不能搭配CMake一起使用,還是個半殘品

結論

在觀望吧,現在還不是成熟的時刻

C++ 的Serialization / Deserialization已經很成熟了
有了Boost 和 Cereal可以選,不過它們需要ifstreamofstream做參數傳入
如果我們需要用於網路傳輸跟Database該怎麼辦
Boost ASIO幫我們解決了這個問題

Output Wrapper

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
template <typename Container, typename SinkType, typename CharType>
class container_sink
{
public:
typedef CharType char_type;
typedef boost::iostreams::sink_tag category;

container_sink(Container& container)
: container_(container)
{
static_assert(sizeof(SinkType) == sizeof(CharType), "invalid size");
}

std::streamsize write(const char_type* buffer, std::streamsize size)
{
const auto safe_sink = reinterpret_cast<const SinkType*>(buffer);
container_.insert(container_.end(), safe_sink, safe_sink + size);
return size;
}

private:
Container& container_;
};

template <typename Container>
using byte_sink = container_sink<Container, uint8_t, char>;
using data_sink = boost::iostreams::stream<byte_sink<data_chunk>>;

如何使用

1
2
3
4
5
6
7
data_chunk data;
data_sink ostream(data);
boost::archive::binary_oarchive oa(ostream);
const gps_position g(35, 59, 24.567f);
oa << g;
ostream.flush();
return ostream;

Input Wrapper

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
template <typename Container, typename SourceType, typename CharType>
class container_source
{
public:
typedef CharType char_type;
typedef boost::iostreams::source_tag category;

container_source(const Container& container)
: container_(container), position_(0)
{
static_assert(sizeof(SourceType) == sizeof(CharType), "invalid size");
}

std::streamsize read(char_type* buffer, std::streamsize size)
{
const auto amount = container_.size() - position_;
const auto result = std::min(size,
static_cast<std::streamsize>(amount));

// TODO: use ios eof symbol (template-based).
if (result <= 0)
return -1;

const auto value = static_cast<typename Container::size_type>(result);
const auto limit = position_ + value;
const auto start = container_.begin() + position_;

const auto end = container_.begin() + limit;
std::copy(start, end, buffer);
position_ = limit;
return result;
}

private:
const Container& container_;
typename Container::size_type position_;
};

template <typename Container>
using byte_source = container_source<Container, uint8_t, char>;
using data_source = boost::iostreams::stream<byte_source<data_chunk>>;

如何使用

1
2
3
4
data_chunk data;
data_source istream(data);
boost::archive::binary_iarchive ia(istream);
istream >> g;