C++ Concepts 的两个技巧

其实是 C++ 模板的技巧,本文包含两个方面:实现模板函数的“偏特化”的效果,以及阻止字符串字面量隐式转换成 bool。

模板函数“偏特化”

模板函数是不能偏特化的,模板类偏特化的语法用到模板函数上编译器是不认识的。
但是可以用一些模板技巧来实现偏特化的效果,当然本质还是函数的重载。这里用 C++ Concepts 来实现,其实用 SFINAE 也可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 默认模板函数,返回类型的默认值(默认构造函数)
template <typename T>
T GetValue()
{
return {};
}

// 该函数相当于 GetValue 对于 bool 的偏特化
// 对于 bool 类型,需要返回 true,而不是其默认值(false)
// 使用 C++ Concepts 添加约束,只有当类型 T == bool 时才实例化这个函数
template <typename T>
requires std::same_as<T, bool>
bool GetValue()
{
return true;
}

字符串字面量隐式转换成bool

考虑下面代码所实现的 print 函数,它有两个重载,一个用来打印 bool 值,一个用来打印任意字符串。
下面的代码看起来没什么问题,但当 print 函数遇到字符串字面量(string literal)时,编译器会选择调用打印 bool 值的那个重载,而不是打印字符串的那个重载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void print(bool v)
{
cout << std::boolalpha << v << endl;
}

void print(string_view v)
{
cout << std::format("\"{}\"", v) << endl;
}

int main()
{
print(false); // false
print(string("Hello")); // "Hello"
print("Hello"); // true (not "Hello")
}

这是因为字符串字面量的本质是个数组(const char[]),编译器首先把它退化成了指针(const char*),然后隐式地将它转换为了 bool 值。

这个“从指针到 bool”的隐式转换通常用来判断一个指针是否等于 NULL。

1
2
3
4
5
int* p = static_cast<int*>(malloc(sizeof(int[4])));
if(p)
{
// do sth...
}

如果要阻止这种隐式转换,可以考虑的一种方案是让打印 bool 值的重载变成模板函数,然后用 C++ concepts 限制它只能接受 bool 值。

1
2
3
4
5
6
template<typename T>
requires std::same_as<T, bool>
void print(T v)
{
cout << std::boolalpha << v << endl;
}

其他的重载不需要修改,然后代码就可以正常工作了。

作者

uint128.com

发布于

2023-04-29

更新于

2023-04-29

许可协议

评论