C++14 函数返回类型推导的适用场景

函数返回类型推导(Return Type Deduction for Normal Functions)是C++14中新增的特性。

C++11允许lambda函数根据return语句的表达式类型推断返回类型。C++14为一般的函数也提供了这个能力。C++14还拓展了原有的规则,使得函数体并不是{ return expression; }形式的函数也可以使用返回类型推导。

为了启用返回类型推导,函数声明必须将auto作为返回类型,但没有C++11的后置返回类型说明符:

1
auto DeduceReturnType(); // 返回类型由编译器推断

如果函数实现中含有多个return语句,这些表达式必须可以推断为相同的类型。

使用返回类型推导的函数可以前向声明,但在定义之前不可以使用。它们的定义在使用它们的翻译单元(translation unit)之中必须是可用的。

这样的函数中可以存在递归,但递归调用必须在函数定义中的至少一个return语句之后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
auto Correct(int i) {
if (i == 1) {
return i; // 返回类型被推断为 int
}
else {
return Correct(i-1)+i; // 正确,可以调用
}
}

auto Wrong(int i) {
if (i != 1) {
return Wrong(i-1)+i; // 不能调用,之前没有return语句
}
else {
return i; // 返回类型被推断为 int
}
}

上面描述了这种特性的语法规则,那么适合用在什么地方呢?

最近向Metazion库里增加HashMap时,便遇到了这种需求。

Metazion里的HashMap默认为1024个桶,每个桶的数据结构采用Map实现。下面是未使用该特性时的代码。

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
template<typename KeyType
, typename ValueType
, typename CompareType = LessCompare<KeyType>
, typename HasherType = IntegerHasher<KeyType>
, int BUCKETSIZE = 1024
, typename AllocatorFamily = HeapAllocator<>
>
class HashMap {
DISALLOW_COPY_AND_ASSIGN(HashMap)

using Key_t = KeyType;
using Value_t = ValueType;
using Compare_t = CompareType;
using Hasher_t = HasherType;
using Allocator_t = AllocatorFamily;
using Bucket_t = Map<Key_t, Value_t, Compare_t, Allocator_t>;
using BucketEntry_t = Pair<Key_t, Value_t>;
using BucketIterator_t = typename Bucket_t::Iterator_t;

class Iterator {
friend class HashMap;

public:
Iterator()
: m_owner(nullptr)
, m_bucket(0) {}

Iterator(const Iterator& other)
: m_owner(other.m_owner)
, m_bucket(other.m_bucket)
, m_iter(other.m_iter) {}

Iterator(HashMap* owner, int bucket, BucketIterator_t iter)
: m_owner(owner)
, m_bucket(bucket)
, m_iter(iter) {}

~Iterator() {}

Iterator& operator =(const Iterator& other) {
if (&other != this) {
m_owner = other.m_owner;
m_bucket = other.m_bucket;
m_iter = other.m_iter;
}
return *this;
}

BucketEntry_t& operator *() {
return m_iter.operator *();
}

BucketEntry_t* operator ->() {
return m_iter.operator ->();
}

...

HashMap的迭代器,重载*和->符号时,希望能直接返回桶类型Map相对应迭代器的返回值。但Map的模板参数是Key_t和Value_t,其迭代器返回值类型Entry_t是Pair<Key_t, Value_t>,属于其内部实现细节,不应该对外暴露。另一种方式只能是在HashMap里定义一种与Entry_t相同的类型BucketEntry_t,这样能工作。但在设计上,BucketEntry_t使得HashMap迭代器对桶类型Map形成了依赖,一边改动,另一边也要相应修改,这种耦合是不必要的。

函数返回类型 推导即适用于这种场景,通过将返回值定义为 auto,无需再定义适配类型BucketEntry_t,代码变得简洁,也降低了耦合。

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
template<typename KeyType
, typename ValueType
, typename CompareType = LessCompare<KeyType>
, typename HasherType = IntegerHasher<KeyType>
, int BUCKETSIZE = 1024
, typename AllocatorFamily = HeapAllocator<>
>
class HashMap {
DISALLOW_COPY_AND_ASSIGN(HashMap)

using Key_t = KeyType;
using Value_t = ValueType;
using Compare_t = CompareType;
using Hasher_t = HasherType;
using Allocator_t = AllocatorFamily;
using Bucket_t = Map<Key_t, Value_t, Compare_t, Allocator_t>;
using BucketIterator_t = typename Bucket_t::Iterator_t;

class Iterator {
friend class HashMap;

...

auto& operator *() {
return m_iter.operator *();
}

auto* operator ->() {
return m_iter.operator ->();
}

...

完整代码在这里