0%

Extension Method in C++

從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的基本觀念才比較容易理解