tipo de espaço reservado como parâmetro de modelo
Desde
C++17
, você pode usar tipos de espaço reservado (
auto
e
decltype(auto)
) como
tipos para parâmetros de modelo não tipo
. Isso significa que podemos escrever código genérico para lidar com diferentes tipos de parâmetros de modelo não tipo.
Usando auto
parâmetros de modelo
Desde C++17
, você pode auto
declarar parâmetros de modelo não tipo com . Por exemplo:
#include <iostream>
using namespace std;
template<auto N> struct S {
S(){
cout <<" S Constructor " << N <<endl;}
};
Isso nos permite instanciar parâmetros de modelo não tipo para diferentes tipos N
:
int main() {
S<42> s1; // OK:S中N的类型是int
S<'A'> s2;// OK:S中N的类型是char
}
O resultado da operação é o seguinte:
S Constructor 42
S Constructor A
O código de pré-processamento é o seguinte:
#include <iostream>
using namespace std;
template<auto N>
struct S
{
inline S()
{
operator<<(operator<<(std::operator<<(std::cout, " S Constructor "), N), endl);
}
};
/* First instantiated from: insights.cpp:10 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct S<42>
{
inline S()
{
std::operator<<(std::cout, " S Constructor ").operator<<(42).operator<<(std::endl);
}
};
#endif
/* First instantiated from: insights.cpp:11 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct S<'A'>
{
inline S()
{
std::operator<<(std::operator<<(std::cout, " S Constructor "), 'A').operator<<(std::endl);
}
};
#endif
int main()
{
S<42> s1 = S<42>();
S<'A'> s2 = S<'A'>();
return 0;
}
No entanto, você não pode usar esse recurso para instanciar alguns tipos que não são permitidos como parâmetros de modelo:
S<2.5> s3; // ERROR:模板参数的类型不能是double
Podemos até usar a versão especificada por tipo como uma especialização parcial :
template<int N> class S<N> {
};
O código de exemplo é o seguinte:
#include <iostream>
using namespace std;
template <auto N>
struct S {
S() {
cout << " S Constructor " << N << endl; }
};
template<long N> struct S<N> {
S() {
cout << " S Constructor special " << N << endl; }
};
int main() {
S<42> s1;
S<'A'> s2;
S<42l> s3;
}
Existe até suporte para dedução de argumento de modelo de classe . Por exemplo:
template<typename T, auto N>
class A {
public:
A(const std::array<T, N>&) {
}
A(T(&)[N]) {
}
...
};
Esta classe pode deduzir T
o tipo, N
tipo e N
valor de:
A a2{
"hello"}; // OK,推导为A<const char, 6>,N的类型是std::size_t
std::array<double, 10> sa1;
A a1{
sa1}; // OK,推导为A<double, 10>,N的类型是std::size_t
O código de pré-processamento é o seguinte:
#include <array>
#include <iostream>
using namespace std;
template<typename T, auto N>
class A
{
public:
inline A(const std::array<T, N> &)
{
}
inline A(T (&)[N])
{
}
};
/* First instantiated from: insights.cpp:13 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class A<const char, 6>
{
public:
inline A(const std::array<const char, 6> &);
inline A(const char (&)[6])
{
}
};
#endif
/* First instantiated from: insights.cpp:15 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class A<double, 10>
{
public:
inline A(const std::array<double, 10> &)
{
}
inline A(double (&)[10]);
};
#endif
/* First instantiated from: insights.cpp:15 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
A(const std::array<double, 10> &) -> A<double, 10>;
#endif
/* First instantiated from: insights.cpp:13 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
A(const char (&)[6]) -> A<const char, 6>;
#endif
int main()
{
A<const char, 6> a2 = A<const char, 6>{
"hello"};
std::array<double, 10> sa1 = std::array<double, 10>();
A<double, 10> a1 = A<double, 10>{
sa1};
return 0;
}
Você também pode modificar auto
, por exemplo, para garantir que o tipo de parâmetro seja um ponteiro :
um parâmetro de modelo sem tipo pode ser um ponteiro, mas o ponteiro deve apontar para um objeto de link externo , uma explicação detalhada desse uso
template<const auto* P> struct S; //
Como alternativa, usando modelos variáveis, você pode instanciar modelos com vários parâmetros de modelo de diferentes tipos :
template<auto... VS> class HeteroValueList {
};
Também é possível usar vários parâmetros do mesmo tipo :
template<auto V1, decltype(V1)... VS> class HomoValueList {
};
Um exemplo completo é o seguinte:
#include <array>
#include <iostream>
using namespace std;
template <const auto* P>
struct S {
S() {
cout << "S = " << (P) << endl; }
};
template <auto... VS>
class HeteroValueList {
public:
HeteroValueList() {
cout << "HeteroValueList = " << (... + VS) << endl; }
};
template <auto V1, decltype(V1)... VS>
class HomoValueList {
public:
HomoValueList() {
cout << "HomoValueList V1= " << V1 << " other = " << (... + VS) << endl;
}
};
int main() {
static char str1[] = "Test 1";
S<str1> x;
HeteroValueList<1, 2, 3> vals1; // OK
HeteroValueList<1, 'a', true> vals2; // OK
HomoValueList<1, 2, 3> vals3; // OK
HomoValueList<1, 'a', 3> vals4; // OK
}
O resultado da operação é o seguinte:
S = Test 1
HeteroValueList = 6
HeteroValueList = 99
HomoValueList V1= 1 other = 5
HomoValueList V1= 1 other = 100
O código pré-compilado é o seguinte:
#include <array>
#include <iostream>
using namespace std;
template<const auto * P>
struct S
{
inline S()
{
operator<<(operator<<(std::operator<<(std::cout, "S = "), (P)), endl);
}
};
/* First instantiated from: insights.cpp:26 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct S<&str1>
{
inline S()
{
std::operator<<(std::operator<<(std::cout, "S = "), (str1)).operator<<(std::endl);
}
};
#endif
template<auto ...VS>
class HeteroValueList
{
public:
inline HeteroValueList()
{
operator<<(operator<<(std::operator<<(std::cout, "HeteroValueList = "), (... + VS)), endl);
}
};
/* First instantiated from: insights.cpp:27 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class HeteroValueList<1, 2, 3>
{
public:
inline HeteroValueList()
{
std::operator<<(std::cout, "HeteroValueList = ").operator<<((1 + 2) + 3).operator<<(std::endl);
}
};
#endif
/* First instantiated from: insights.cpp:28 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class HeteroValueList<1, 'a', true>
{
public:
inline HeteroValueList()
{
std::operator<<(std::cout, "HeteroValueList = ").operator<<((1 + static_cast<int>('a')) + static_cast<int>(true)).operator<<(std::endl);
}
};
#endif
template<auto V1, decltype(V1) ...VS>
class HomoValueList
{
public:
inline HomoValueList()
{
operator<<(operator<<(operator<<(operator<<(std::operator<<(std::cout, "HomoValueList V1= "), V1), " other = "), (... + VS)), endl);
}
};
/* First instantiated from: insights.cpp:29 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class HomoValueList<1, 2, 3>
{
public:
inline HomoValueList()
{
std::operator<<(std::operator<<(std::cout, "HomoValueList V1= ").operator<<(1), " other = ").operator<<(2 + 3).operator<<(std::endl);
}
};
#endif
/* First instantiated from: insights.cpp:30 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class HomoValueList<1, 97, 3>
{
public:
inline HomoValueList()
{
std::operator<<(std::operator<<(std::cout, "HomoValueList V1= ").operator<<(1), " other = ").operator<<(97 + 3).operator<<(std::endl);
}
};
#endif
int main()
{
static char str1[7] = "Test 1";
S<&str1> x = S<&str1>();
HeteroValueList<1, 2, 3> vals1 = HeteroValueList<1, 2, 3>();
HeteroValueList<1, 'a', true> vals2 = HeteroValueList<1, 'a', true>();
HomoValueList<1, 2, 3> vals3 = HomoValueList<1, 2, 3>();
HomoValueList<1, 97, 3> vals4 = HomoValueList<1, 97, 3>();
return 0;
}
parâmetros de modelo de caractere e string
É possível definir um parâmetro de modelo que pode ser um caractere ou uma string . Por exemplo, podemos melhorar o método de saída de qualquer número de argumentos com expressões de dobra como esta:
#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';
}
Definindo o separador de argumento padrão Sep
para espaços, podemos obter o mesmo efeito de antes:
template<auto Sep = ' ', typename First, typename... Args>
void print(const First& firstarg, const Args&... args) {
...
}
Ainda podemos chamar como antes:
std::string s{
"world"};
print(7.5, "hello", s); // 打印出:7.5 hello world
No entanto, Sep
também podemos especificar explicitamente outro caractere como separador, parametrizando o separador:
print<'-'>(7.5, "hello", s); // 打印出:7.5-hello-world
Mesmo, por causa do uso de auto
, podemos até mesmo passar strings literais declaradas como desvinculadas como delimitadores:
static const char sep[] = ", ";
print<sep>(7.5, "hello", s); // 打印出:7.5, hello, world
Como alternativa, podemos passar qualquer outro tipo que possa ser usado como parâmetro de modelo:
print<-11>(7.5, "hello", s); // 打印出:7.5-11hello-11world
Definir constantes de metaprogramação
Mais fácil de definir constantes de tempo de compilação.
Originalmente o seguinte código:
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>;
Isso agora pode ser implementado simplesmente como:
template<auto v>
struct constant
{
static constexpr auto value = v;
};
using i = constant<42>;
using c = constant<'x'>;
using b = constant<true>;
Exemplo completo:
#include <array>
#include <iostream>
using namespace std;
template <auto v>
struct constant {
static constexpr auto value = v;
};
using i = constant<42>;
using c = constant<'x'>;
using b = constant<true>;
int main() {
i jj;
c jj1;
b jj2;
}
O código de pré-processamento é o seguinte:
#include <array>
#include <iostream>
using namespace std;
template<auto v>
struct constant
{
inline static constexpr const auto value = v;
};
/* First instantiated from: insights.cpp:14 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct constant<42>
{
inline static constexpr const int value = 42;
// inline constexpr constant() noexcept = default;
};
#endif
/* First instantiated from: insights.cpp:15 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct constant<'x'>
{
inline static constexpr const char value = 'x';
// inline constexpr constant() noexcept = default;
};
#endif
/* First instantiated from: insights.cpp:16 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct constant<true>
{
inline static constexpr const bool value = true;
// inline constexpr constant() noexcept = default;
};
#endif
using i = constant<42>;
using c = constant<'x'>;
using b = constant<true>;
int main()
{
constant<42> jj = constant<42>();
constant<'x'> jj1 = constant<'x'>();
constant<true> jj2 = constant<true>();
return 0;
}
Da mesma forma, o seguinte código original:
template<typename T, T... Elements>
struct sequence {
};
using indexes = sequence<int, 0, 3, 4>;
Isso agora pode ser implementado simplesmente como:
template<auto... Elements>
struct sequence {
};
using indexes = sequence<0, 3, 4>;
O código de pré-processamento é o seguinte:
template<typename T, T ...Elements>
struct sequence
{
};
/* First instantiated from: insights.cpp:11 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct sequence<int, 0, 3, 4>
{
// inline constexpr sequence() noexcept = default;
};
#endif
using indexes = sequence<int, 0, 3, 4>;
int main()
{
sequence<int, 0, 3, 4> i = sequence<int, 0, 3, 4>();
return 0;
}
Agora você pode até definir um objeto de tempo de compilação (semelhante a um simples ) que contém valores de vários tipos diferentes :tuple
using tuple = sequence<0, 'h', true>;
Usando auto
parâmetros como modelos de variáveis
Você também pode auto
implementar modelos variáveis usando como parâmetros de modelo .
Por exemplo, a declaração a seguir define um modelo de variável arr
cujos parâmetros de modelo são o tipo e o número de elementos:
template<typename T, auto N> std::array<T, N> arr;
Dentro de cada unidade de compilação, todas arr<int, 10>
as referências a apontarão para o mesmo objeto global. And arr<long, 10>
e arr<int, 10u>
apontará para outros objetos (cada um disponível em todas as unidades de compilação). Como um exemplo completo, considere o seguinte arquivo de cabeçalho:
#ifndef VARTMPLAUTO_HPP
#define VARTMPLAUTO_HPP
#include <array>
template<typename T, auto N> std::array<T, N> arr{
};
void printArr();
#endif // VARTMPLAUTO_HPP
Aqui, podemos modificar diferentes instâncias de dois modelos variáveis dentro de uma unidade de compilação:
#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();
}
Esses dois modelos variáveis podem ser impressos em outra unidade de compilação:
#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';
}
A saída do programa será:
arr<int, 5>: 17 0 0 42 0
arr<int, 5u>: 0 11 0 33 0
Da mesma forma que você pode declarar um modelo de variável constante de qualquer tipo, o tipo pode ser deduzido do valor inicial:
template<auto N> constexpr auto val = N; // 自从C++17起OK
Pode então ser usado assim:
auto v1 = val<5>; // v1 == 5,v1的类型为int
auto v2 = val<true>; // v2 == true,v2的类型为bool
auto v3 = val<'a'>; // v3 == 'a',v3的类型为char
Aqui está o que está acontecendo é explicado:
std::is_same_v<decltype(val<5>), int> // 返回false
std::is_same_v<decltype(val<5>), const int> // 返回true
std::is_same_v<decltype(v1), int> // 返回true(因为auto会退化)
Usando decltype(auto)
parâmetros de modelo
Agora você também pode usar outro tipo de espaço reservado decltype(auto)
( C++14
importação) como um parâmetro de modelo. Observe que a derivação desse tipo de espaço reservado tem regras muito especiais. A decltype
regra raiz, se usada decltype(auto)
para deduzir expressões (expressões) em vez de nomes de variáveis, o resultado da dedução dependerá do tipo de valor da expressão:
- prvalue (como uma variável temporária) deduz o tipo
- lvalue (por exemplo, objeto nomeado) deduz o tipo&
- xvalue (por exemplo, marcado com
std::move()
objeto) deduz type&&
, o que significa que você pode facilmente deduzir argumentos de modelo como referências, o que pode levar a alguns efeitos surpreendentes.
Por exemplo:
#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; // N的类型推导为const int 42
S<(c)> s2; // N的类型推导为const int&,N是c的引用
s1.printN();
s2.printN();
S<(v)> s3; // N的类型推导为int&,N是v的引用
v = 77;
s3.printN(); // 打印出:N: 77
}
Os resultados da execução são os seguintes;
N: 42
N: 42
N: 77
O código de pré-processamento é o seguinte:
#include <iostream>
template<decltype(auto) N>
struct S
{
inline void printN() const
{
(std::operator<<(std::cout, "N: ") << N) << '\n';
}
};
/* First instantiated from: insights.cpp:15 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct S<42>
{
inline void printN() const
{
std::operator<<(std::operator<<(std::cout, "N: ").operator<<(42), '\n');
}
// inline constexpr S() noexcept = default;
};
#endif
/* First instantiated from: insights.cpp:16 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct S<&c>
{
inline void printN() const
{
std::operator<<(std::operator<<(std::cout, "N: ").operator<<(c), '\n');
}
// inline constexpr S() noexcept = default;
};
#endif
/* First instantiated from: insights.cpp:20 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct S<&v>
{
inline void printN() const
{
std::operator<<(std::operator<<(std::cout, "N: ").operator<<(v), '\n');
}
// inline constexpr S() noexcept = default;
};
#endif
static const int c = 42;
static int v = 42;
int main()
{
S<42> s1 = S<42>();
S<&c> s2 = S<&c>();
s1.printN();
s2.printN();
S<&v> s3 = S<&v>();
v = 77;
s3.printN();
return 0;
}