C++17完整导引-模板特性之占位符类型模板参数

这篇具有很好参考价值的文章主要介绍了C++17完整导引-模板特性之占位符类型模板参数。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


自从 C++17起,你可以使用占位符类型( autodecltype(auto))作为 非类型模板参数的类型。这意味着我们可以写出泛型代码来处理不同类型的非类型模板参数。

使用auto模板参数

自从C++17起,你可以使用auto来声明非类型模板参数。例如:

#include <iostream>
using namespace std;

template<auto N> struct S {
    S(){cout <<" S Constructor " << N <<endl;}
};

这允许我们为不同类型实例化非类型模板参数N

int main() {
   S<42> s1; // OK:S中N的类型是int
   S<'A'> s2;// OK:S中N的类型是char
}

运行结果如下:

 S Constructor 42
 S Constructor A

预处理代码如下:

#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;
}

然而,你不能使用这个特性来实例化一些不允许作为模板参数的类型:

S<2.5> s3;  // ERROR:模板参数的类型不能是double

我们甚至还可以用指明类型的版本作为部分特化版

template<int N> class S<N> {};

示例代码如下:

#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;
}

甚至还支持类模板参数推导。例如:

template<typename T, auto N>
class A {
public:
    A(const std::array<T, N>&) {
    }
    A(T(&)[N]) {
    }
    ...
};

这个类可以推导出T的类型、N的类型、N的值:

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

预处理代码如下:

#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;
}

你也可以修饰auto,例如,可以确保参数类型必须是个指针
非类型模板参数可以是指针,但该指针必须指向外部链接对象,此项使用的详细解释

template<const auto* P> struct S; //

另外,通过使用可变参数模板,你可以使用多个不同类型的模板参数来实例化模板

template<auto... VS> class HeteroValueList {
};

也可以用多个相同类型的参数

template<auto V1, decltype(V1)... VS> class HomoValueList {
};

完整示例如下:

#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
}

运行结果如下:

S = Test 1
HeteroValueList = 6
HeteroValueList = 99
HomoValueList V1= 1 other = 5
HomoValueList V1= 1 other = 100

预编译代码如下:

#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;
}

字符和字符串模板参数

可以定义一个既可能是字符也可能是字符串的模板参数。例如,我们可以像下面这样改进用折叠表达式输出任意数量参数的方法:

#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';
}

将默认的参数分隔符Sep设置为空格,我们可以实现和之前相同的效果:

template<auto Sep = ' ', typename First, typename... Args>
void print(const First& firstarg, const Args&... args) {
    ...
}

我们仍然可以像之前一样调用:

std::string s{"world"};
print(7.5, "hello", s);      // 打印出:7.5 hello world

然而,通过把分隔符Sep参数化,我们也可以显示指明另一个字符作为分隔符:

print<'-'>(7.5, "hello", s); // 打印出:7.5-hello-world

甚至,因为使用了auto,我们甚至可以传递被声明为无链接的字符串字面量作为分隔符:

static const char sep[] = ", ";
print<sep>(7.5, "hello", s); // 打印出:7.5, hello, world

另外,我们也可以传递任何其他可以用作模板参数的类型:

print<-11>(7.5, "hello", s); // 打印出:7.5-11hello-11world

定义元编程常量

定义编译期常量的更加容易。

原本的下列代码:

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>;

现在可以简单的实现为:

template<auto v>
struct constant
{
    static constexpr auto value = v;
};

using i = constant<42>;
using c = constant<'x'>;
using b = constant<true>;

完整示例:

#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;
}

预处理代码如下:

#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;
}

同样,原本的下列代码:

template<typename T, T... Elements>
struct sequence {
};

using indexes = sequence<int, 0, 3, 4>;

现在可以简单的实现为:

template<auto... Elements>
struct sequence {
};

using indexes = sequence<0, 3, 4>;

预处理代码如下:

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;
}

你现在甚至可以定义一个持有若干不同类型的值的编译期对象(类似于一个简单的tuple):

using tuple = sequence<0, 'h', true>;

使用auto作为变量模板的参数

你也可以使用auto作为模板参数来实现 变量模板(variable templates)
例如,下面的声明定义了一个变量模板arr,它的模板参数分别是元素的类型和数量:

template<typename T, auto N> std::array<T, N> 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

这里,我们可以在一个编译单元内修改两个变量模板的不同实例:

#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();
}

另一个编译单元内可以打印这两个变量模板:

#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';
}

程序的输出将是:

arr<int, 5>:  17 0 0 42 0
arr<int, 5u>: 0 11 0 33 0

用同样的方式你可以声明一个任意类型的常量变量模板,类型可以通过初始值推导出来:

template<auto N> constexpr auto val = N; // 自从C++17起OK

之后可以像下面这样使用:

auto v1 = val<5>;       // v1 == 5,v1的类型为int
auto v2 = val<true>;    // v2 == true,v2的类型为bool
auto v3 = val<'a'>;     // v3 == 'a',v3的类型为char

这里解释了发生了什么:

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会退化)

使用decltype(auto)模板参数

你现在也可以使用另一个占位类型decltype(auto)C++14引入)作为模板参数。注意,这个占位类型的推导有非常特殊的规则。根decltype的规则,如果使用decltype(auto)来推导 表达式(expressions) 而不是变量名,那么推导的结果将依赖于表达式的值类型:

  • 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;        // 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
}

运行结果如下;

N: 42
N: 42
N: 77

预处理代码如下:文章来源地址https://www.toymoban.com/news/detail-455854.html

#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;
}

到了这里,关于C++17完整导引-模板特性之占位符类型模板参数的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 非类型模板参数

    背景 除了日常用的typename以外,模板还有很多非类型模板参数 具体 int 或者 enums 对象指针和函数指针(好像很多编译器都支持void*) 左值引用(用的不多) 模板模板类型 模板模板类型 其他都比较常见且容易理解,重点看看这个: Base前面的typename在C++11之前必须是class,C++11以后才

    2024年02月15日
    浏览(23)
  • 【C++】模板进阶--非类型模板参数&&模板特化及分离编译

    模板参数分为 类型形参 与 非类型形参 ,其中,类型形参即出现在模板参数列表中,跟在class或者typename之类的参数类型名称,非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用 我们以定义一个静态的数组为例,在没有非

    2023年04月23日
    浏览(33)
  • C++11可变参数模板(typename... Args模板参数包或class... Args)(Args... args函数参数包)(递归展开与参数包展开(只支持C++17))

    C++可变参数是指函数的参数个数是可变的,可以在函数定义时不确定参数的个数,需要在函数体内通过特定的语法来处理这些参数。C++11标准引入了新的可变参数模板,使得可变参数的处理更加方便和灵活。在函数定义时,可以使用省略号(…)来表示可变参数,然后通过va_li

    2024年02月08日
    浏览(35)
  • [C++] 模板进阶(非类型模板参数,特化,分离编译)

    模板参数分类类型形参与非类型形参。 类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。 非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。 我们举例来看一下: 注意: 1. 浮点数、类对象

    2024年02月04日
    浏览(27)
  • 【C++】模板进阶—非类型模板参数、模板特化及模板的分离编译

    🚀 作者简介:一名在后端领域学习,并渴望能够学有所成的追梦人。 🚁 个人主页:不 良 🔥 系列专栏:🛸C++  🛹Linux 📕 学习格言:博观而约取,厚积而薄发 🌹 欢迎进来的小伙伴,如果小伙伴们在学习的过程中,发现有需要纠正的地方,烦请指正,希望能够与诸君一同

    2024年02月16日
    浏览(26)
  • 【C++学习】模板进阶——非类型模板参数 | 模板的特化 | 分离编译

    🐱作者:一只大喵咪1201 🐱专栏:《C++学习》 🔥格言: 你只管努力,剩下的交给时间! 模板我们之前一直都在使用,尤其是在模拟STL容器的时候,可以说,模板给类增加了更多的可能性,是C++最重要的部分之一。下面本喵来更深入的讲解一下模板。 在上面代码中,创建了

    2023年04月13日
    浏览(24)
  • 现代C++新特性 扩展的聚合类型(C++17 C++20)(PC浏览效果更佳)

         文字版PDF文档链接:现代C++新特性(文字版)-C++文档类资源-CSDN下载  C++17标准对聚合类型的定义做出了大幅修改,即从基类公开且非虚继承的类也可能是一个聚合。同时聚合类型还需要满足常规条件。 1.没有用户提供的构造函数。 2.没有私有和受保护的非静态数据成

    2024年02月16日
    浏览(28)
  • C++函数模板、特例化、非类型参数、类模板、allocator

    模板对类型能进行参数化成【模板参数】,输入的是类型,生成的是代码。使用的时候,每指定一份类型,模板就会根据类型生成一份新的代码(比如函数模板实例化生成的是【模板函数】),有利于减少代码量,通过较少的代码也能实现函数重载。 调用函数模板的时候,一

    2024年02月21日
    浏览(31)
  • 【C++初阶(十)】C++模板(进阶) ---非类型模板参数、模板的特化以及模板的分离编译

    本专栏内容为:C++学习专栏,分为初阶和进阶两部分。 通过本专栏的深入学习,你可以了解并掌握C++。 💓博主csdn个人主页:小小unicorn ⏩专栏分类:C++ 🚚代码仓库:小小unicorn的代码仓库🚚 🌹🌹🌹关注我带你学习编程知识 模板参数可分为类型形参和非类型形参。 类型形

    2024年01月18日
    浏览(22)
  • JDK8-JDK17中的新特性(var类型推断、模式匹配、Record、密封类)

    新的语法结构,勾勒出了 Java 语法进化的一个趋势,将开发者从 复杂、繁琐 的低层次抽象中逐渐解放出来,以更高层次、更优雅的抽象,既 降低代码量 ,又避免意外编程错误的出现,进而提高代码质量和开发效率。 1.1 Java的REPL工具: jShell命令 JDK9的新特性 Java 终于拥有了

    2024年02月06日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包