13 Placeholder Types like auto as Template Parameters
从 C++17 开始,可以使用占位符 auto
decltype(auto)
作为非模板类型的类型,这样可以写出不同类型的非模板类型的泛型代码。
Using auto
for Template Parameters
可以使用 auto
声明非类型模板参数。比如如下声明,可以用不同类型来初始化非类型模板参数 N
,但是不能使用不支持的类型来实例化模板。
template <auto N>
class S
{
};
S<42> s1; // OK: type of N in S is int
S<'a'> s2; // OK: type of N in S is char
S<2.5> s3; // ERROR: template parameter type still cannot be double
N
的类型和值。
template <typename T, auto N>
class A
{
public:
A(const std::array<T, N> &)
{
}
A(T (&)[N])
{
}
};
A a2{"hello"}; // OK, deduces A<const char, 6> with N being std::size_t
std::array<double, 10> sa1;
A a1{sa1}; // OK, deduces A<double, 10> with N being std::size_t
auto
,比如下面要求参数模板必须是指针。
也可以使用可变参数模板,这样可以用多个参数类型相同或者不同的模板参数来实例化。
template <auto... VS>
class HeteroValueList
{
};
template <auto V1, decltype(V1)... VS>
class HomoValueList
{
};
HeteroValueList<1, 2, 3> vals1; // OK
HeteroValueList<1, 'a', true> vals2; // OK
HomoValueList<1, 2, 3> vals3; // OK
HomoValueList<1, 'a', true> vals4; // ERROR
Parameterizing Templates for Characters and Strings
这个特性的一个应用就是可以定义一个模板参数,其可能是一个字符,也可能是一个字符串。比如可以改造使用折叠表达式打印任意个参数的例子,增加一个分隔符,默认是空格。
#include <iostream>
template <auto Sep = ' ', typename First, typename... Args>
void print(const First &first, const Args &...args)
{
std::cout << first;
auto outWithSep = [](const auto &arg)
{
std::cout << Sep << arg;
};
(..., outWithSep(args));
std::cout << '\n';
}
std::string s{"world"};
print(7.5, "hello", s); // prints: 7.5 hello world
print<'-'>(7.5, "hello", s); // prints: 7.5-hello-world
static const char sep[] = ", ";
print<sep>(7.5, "hello", s); // prints: 7.5, hello, world
print<-11>(7.5, "hello", s); // prints: 7.5-11hello-11world
Defining Metaprogramming Constants
auto
模版参数的另一个应用是简化编译期常量的定义。下面是简化的例子。
template <typename T, T v>
struct constant
{
static constexpr T value = v;
};
using i = constant<int, 42>;
using c = constant<char, 'x'>;
using b = constant<bool, true>;
// now
template <auto v>
struct constant
{
static constexpr auto value = v;
};
using i = constant<42>;
using c = constant<'x'>;
using b = constant<true>;
也可以应用于可变参数模版,甚至声明一系列类型不同的常量,类似一个 tuple
。
template <typename T, T... Elements>
struct sequence
{
};
using indexes = sequence<int, 0, 3, 4>;
// now
template <auto... Elements>
struct sequence
{
};
using indexes = sequence<0, 3, 4>;
using tuple = sequence<0, 'h', true>;
Using auto
as Variable Template Parameter
使用 auto
可以实现变量模板(variable template
)。
比如下面的例子,变量模板 arr
的定义,其出现在头文件中。在每一个编译单元中,所有的 arr<int,10>
都指向同一个对象,与 arr<long,10>
和 arr<int,10u>
指向不同的对象。
下面是头文件中的定义。
#ifndef VARTMPLAUTO_HPP
#define VARTMPLAUTO_HPP
#include <array>
template <typename T, auto N>
std::array<T, N> arr{};
void printArr();
#endif // VARTMPLAUTO_HPP
main
的文件如下
#include "vartmplauto.hpp"
int main()
{
arr<int, 5>[0] = 17;
arr<int, 5>[3] = 42;
arr<int, 5u>[1] = 11;
arr<int, 5u>[3] = 33;
printArr();
}
实现 printArr
的文件如下
#include "vartmplauto.hpp"
#include <iostream>
void printArr()
{
std::cout << "arr<int,5>: ";
for (const auto &elem : arr<int, 5>)
{
std::cout << elem << ' ';
}
std::cout << "\narr<int,5u>: ";
for (const auto &elem : arr<int, 5u>)
{
std::cout << elem << ' ';
}
std::cout << '\n';
}
template <auto N>
constexpr auto val = N; // OK since C++17
auto v1 = val<5>; // v1 == 5, v1 is int
auto v2 = val<true>; // v2 == true, v2 is bool
auto v3 = val<'a'>; // v3 == 'a', v3 is char
std::is_same_v<decltype(val<5>), int>; // yields false
std::is_same_v<decltype(val<5>), const int>; // yields true
std::is_same_v<decltype(v1), int>; // yields true (because auto decays)
Using decltype(auto)
as Template Parameter
我们还可以使用占位符 decltype(auto)
作为模板参数。不过 decltype
推导类型有特殊的规则。对于表达式而言,decltype(auto)
的结果依赖于表达式的类型。
- prvalue,比如临时值,结果是
type
。 - lvalue,比如有名的对象,结果是
type&
。 - xvalue,比如
std::move
的对象,结果是type&&
。
也就是说,结果很容易是引用类型,因此,可能有意想不到的结果。
#include <iostream>
template <decltype(auto) N>
struct S
{
void printN() const
{
std::cout << "N: " << N << '\n';
}
};
static const int c = 42;
static int v = 42;
int main()
{
S<c> s1; // deduces N as const int 42
S<(c)> s2; // deduces N as const int& referring to c
s1.printN();
s2.printN();
S<(v)> s3; // deduces N as int& referring to v
v = 77;
s3.printN(); // prints: N: 77
}