接上文,继续记录学习 C++ Template 这本书的心得。
变参模板
以下是一个例子:
void print() {}
template <typename T, typename... Types>
void print(T firstArg, Types... args) {
std::cout << firstArg << std::endl;
print(args...);
}
上述的代码必须要定义 print()
这个重载函数,否则编译会不通过。因为 Types... args
的参数数量最后会变成 0,这样就找不到对应的函数了,也可以采用如下的版本。
template <typename T>
void print(T arg) {
std::cout << arg << std::endl;
}
template <typename T, typename... Types>
void print(T firstArg, Types... args) {
print(firstArg);
print(args...);
}
当传入的参数为 1 个的时候,会优先匹配第一个函数。
折叠表达式
template <typename... T>
auto foldSum(T... args) { return (... + args); }
变参下标(Variadic Indices)
template <typename T, typename... Idx>
void printElements(const T &v, Idx... idx) {
print(v[idx]...);
}
const char *names[]{"Alice", "Bob", "Peter"};
// print "Alice" and "Bob"
printElements(names, 0, 1);
template <size_t... Idx, typename C> void printElements(const C &v) {
print(v[Idx]...);
}
const char *names[]{"Alice", "Bob", "Peter"};
// print "Bob" and "Peter"
printElements<1, 2>(names);
模板成员变量
template<typename T>
class MyClass {
public:
static constexpr int size = sizeof(T);
};
template<typename T>
constexpr int Size = MyClass<T>::size;
std::cout << Size<char> << std::endl; // 1
std::cout << Size<double> << std::endl; // 8
各种 type trait
,如 std::is_const_v
就是基于此。
从 static_assert 到 std::enable_if_t 再到 concept
static_assert
template <typename T> void foo(T t) {
static_assert(sizeof(T) > 4,
"size of given type should be greater than 4 bytes");
std::cout << t << std::endl;
}
foo(1);
上述代码会报错:
Static assertion failed due to requirement ‘sizeof(int) > 4’: size of given type should be greater than 4 bytesclang(static_assert_requirement_failed)
std::enable_if_t
template <typename T>
std::enable_if_t<(sizeof(T) > 4)> foo(T t) {
std::cout << t << std::endl;
}
std::enable_if_t
等价于 std::enable_if::type
。其中第一个模板类型是 bool
编译期常量,若为 true
,则函数返回第二个模板类型(若没有第二个模板类型,则为 void
),否则函数未定义。
上述代码会报未定义错误:
No matching function for call to ‘foo’
concept
template <typename T>
requires(sizeof(T) > 4)
void foo(T t) {
std::cout << t << std::endl;
}
类似的报错:
No matching function for call to 'foo'clang(ovl_no_viable_function_in_call)
main.cpp(18, 6): Candidate template ignored: constraints not satisfied [with T = int]
main.cpp(17, 12): Because 'sizeof(int) > 4' (4 > 4) evaluated to false
模板中按值传递 & 按引用传递
引用传递会保留类型的修饰,而值传递则会对类型进行退化。
template<typename T>
void foo(T &t) {
if constexpr(std::is_array_v<T>) {
std::cout << "is array" << std::endl;
}
if constexpr(std::is_const_v<T>) {
std::cout << "is const" << std::endl;
}
}
const char a[] = "Hello, World";
foo(a);
// 输出
// is array
// is const
而将上述代码修改为值传递:
template <typename T> void foo(T t) {
if constexpr (std::is_array_v<T>) {
std::cout << "is array" << std::endl;
}
if constexpr (std::is_const_v<T>) {
std::cout << "is const" << std::endl;
}
}
则不会产生任何输出,因为 const char[13]
被退化为了 char *
,
既不是 array
也不是 const
。
下面的函数有时候会产生问题:
template<typename T>
T foo(T t) {
return t;
}
虽然会对类型进行退化,但是仍然可以通过显式的方式将 T
声明为引用。
int i;
foo<int &>(i);
这样可能会导致垂悬引用(dangling reference),所以此处可以通过三种方式保证返回值是退化的。
// 使用 auto 自动退化
template<typename T>
auto foo(T t) {
return t;
}
// 使用 std::decay 显式退化
template<typename T>
std::decay_t<T> foo(T t) {
return t;
}
// 去除引用
template<typename T>
std::remove_reference_t<T> foo(T t) {
return t;
}
std::is_convertible
std::is_convertible
可以用于限制一个使用万能引用的构造函数的模板类型,例如:
class MyClass {
public:
template <typename T>
MyClass(T &&data)
requires(std::is_convertible_v<T, std::string>)
: data(data) {}
private:
std::string data;
};
MyClass a(1); // error
报错信息:
No matching constructor for initialization of 'MyClass'clang(ovl_no_viable_function_in_init)
main.cpp(19, 7): Candidate constructor (the implicit copy constructor) not viable: no known conversion from 'int' to 'const MyClass' for 1st argument
main.cpp(19, 7): Candidate constructor (the implicit move constructor) not viable: no known conversion from 'int' to 'MyClass' for 1st argument
main.cpp(22, 3): Candidate template ignored: constraints not satisfied [with T = int]
main.cpp(23, 14): Because 'std::is_convertible_v<int, std::string>' evaluated to false
std::reference_wrapper
std::string str = "123";
auto r1 = std::ref(str); // std::reference_wrapper<string>
auto r2 = std::cref(str); // std::reference_wrapper<const string>
std::reference_wrapper
相当于在引用外包了一层,这样就可以在保证性能的前提下进行值传递(有些模板采用值传递)。std::reference_wrapper
可以隐式地转换成包裹的引用本身。
std::string &s2 = r1;
const std::string &s3 = r2;