首页 > 编程语言 >跟我学C++中级篇——C++17中的std::void_t

跟我学C++中级篇——C++17中的std::void_t

时间:2024-12-28 12:58:59浏览次数:7  
标签:std 跟我学 void C++ template type 模板

一、std::void_t

std::void_t是从C++17提供的一个元函数,主要用来在SFINAE应用上更简单方便一些。老规矩先看一下其定义形式:

template< class... >
using void_t = void;

这段代码单纯从代码意义上理解有两个情况:一是它使用了变参模板;二是使用了别名应用。它是一种很简单的应用形式,如果知道变参模板就非常容易理解,其实就是把其它N个数据类型定义为void类型。这块知识对于模板老鸟儿来说是不值一哂的,即使一些新手也感觉不到它的复杂和难度,但随之而来的就是,这玩意儿有啥用。太简单的东西,一般来说会让新人无所适从。拿到了所谓的斧头,却不知道该如何用。

二、模板中的应用

进行模板编程特别是元编程的开发者来说,有一个问题。那就是如何在编译期进行条件逻辑的转换和数据类型的处理。还是那句话,对于普通的编程(或者说非编译期展开的编程),条件判断非常简单if等语句可以轻松拿捏。而数据类型的处理对C++这种强类型语言来说更是方便处理。但麻烦就在于编译期很多东西都是不确定的,它未到达运行期,所以根本没办法使用运行时的那一套手段。这也是初学模板和元编程时,很多程序员感到头疼的地方。
逻辑处理仍然是开发者认知的那个逻辑处理,是与非,但形成是与非的这种手段则变了。如果单纯使用SFINAE技术,复杂性和难于理解性和不方便性,几乎同时存在。前面提到过,无论哪种语言整体的方向是朝着简单化的方向进化,C++也是如此。
std::void_t就是为了在SFINAE编程中提供方便,或者说在C++20概念Concepts出现前对SFINAE技术的一种优化手段。有人说可不可以不使用void,使用int或其它的类型来这样使用呢?当然可以。但又引入了其它的限制条件和更多的判断,因为int或其它类型都不如void在C++语言中应用更广泛。
回到如何应用的话题,既然知道它是在SFINAE中应用,那么一个最简单的应用就是判断表达式的合法性,从而进行逻辑选择,特别是在一些不求值的语句中,其更具有优势,如decltype和declval。看一个简单的应用场景:

template< class, class = void >
struct has_type_member : std::false_type { };

template< class T >
struct has_type_member<T, std::void_t<typename T::type>> : std::true_type { };

是不是非常熟悉,在前面的不少的文章中都有类似的代码,用来判断和处理类中是否存在某个成员(可参看前面的“一步一步写线程之八线程池的完善之二数据结构封装”)。同样的手段,稍微变换一下就可以做数据类型的检测是否为希望的类型,如果不是,则编译失效,抛出一个错误。同样不同的逻辑选择用在数据类型的返回上,可以得到不同的数据类型。
这也是前面提到的数据类型的检测处理和逻辑选择。
这里有一个小细节,为什么使用变参模板呢?试想一下,有没有这种开发场景,一个类中,同时存在N个成员,有时候儿需要判断它们同时存在呢?如果明白了这种情况就明白了为什么是变参模板了。否则的话,就得一个个的写,一个一个的判断。亦或者,开发者自己另外实现类似的差别方法。
大家是不是突然觉得,这货是不是和std::enable_if的用法有点类似,只不过一个是元函数一个是元类型。
这里再顺带说明一下,上面的代码中用到了std::false_type,它是由下面的定义而来 :

template< bool B >
using bool_constant = integral_constant<bool, B>;
using true_type	= std::integral_constant<bool, true>
using false_type = std::integral_constant<bool, false>

注意,它也是在C++17支持的。所以说很多的技术都是互相关联产生的,小小的组合可能产生大大的威力。

三、例程

来看一下cppreference上的例程:

#include <iomanip>
#include <iostream>
#include <map>
#include <type_traits>
#include <vector>

// 检查类型是否有 begin() 和 end() 成员函数的变量模板
template<typename, typename = void>
constexpr bool is_iterable = false;

template<typename T>
constexpr bool is_iterable<
    T,
    std::void_t<decltype(std::declval<T>().begin()),
                decltype(std::declval<T>().end())
    >
> = true;

// 迭代器特征,其 value_type 是被迭代容器的 value_type,
// 也支持 value_type 为 void 的 back_insert_iterator

template<typename T, typename = void>
struct iterator_trait : std::iterator_traits<T> {};

template<typename T>
struct iterator_trait<T, std::void_t<typename T::container_type>>
    : std::iterator_traits<typename T::container_type::iterator> {};

class A {};

#define SHOW(...) std::cout << std::setw(34) << #__VA_ARGS__ \
                            << " == " << __VA_ARGS__ << '\n'

int main()
{
    std::cout << std::boolalpha << std::left;
    SHOW(is_iterable<std::vector<double>>);
    SHOW(is_iterable<std::map<int, double>>);
    SHOW(is_iterable<double>);
    SHOW(is_iterable<A>);

    using container_t = std::vector<int>;
    container_t v;

    static_assert(std::is_same_v<
        container_t::value_type,
        iterator_trait<decltype(std::begin(v))>::value_type
    >);

    static_assert(std::is_same_v<
        container_t::value_type,
        iterator_trait<decltype(std::back_inserter(v))>::value_type
    >);
}

通过std::void_t检测类型是否有迭代器的支持。其实它可以拓展到很多,包括智能指针、运算符(++等)甚至是否包含类型嵌套等等。开发者可以在实际的开发过程中活学活用,大胆尝试,不行再写别的方法。
再看一个类型推导的例程:

template<typename T, typename = void>
struct resultOf {
    using type = void;  
};

template<typename T>
struct resultOf<T, std::void_t<decltype(std::declval<T>().resultT())>> {
    using type = decltype(std::declval<T>().resultT());
};

template<typename T>
using resultOf_t = typename resultOf<T>::type;

template<typename T>
resultOf_t<T> CheckType(const T& t) {
    if constexpr (!std::is_same_v<resultOf_t<T>, void>) {
        return t.resultT(); //通过T中的resultT来获取类型
    }
}

多看多用慢慢就会掌握门道,初窥门径后就可以自由的进行组合应用了。

四、总结

std::void_t更好的是在模板和元编程中应用,这也是C++的一个发展的重点。毕竟泛型编程是每个语言都不能舍弃的情怀。所以对大多数程序员来说,std::void_t这个元函数属于一种应用非常少的情况。可以先了解一下,然后再真正遇到后再认真学习。
在这篇普及的文章基础上,后面将对enable_if与std::void_t以及C++20中的std::integral‌进行一次系统的分析说明。这样,会让大家能从整体到细节上对模板和元编程中的相关用法有一个清晰的认识。

标签:std,跟我学,void,C++,template,type,模板
From: https://blog.csdn.net/fpcc/article/details/144778813

相关文章

  • C++日志管理从基础到完善
    万古教员有名言,自信人生二百年。个人主页:oioihoii喜欢内容的话欢迎关注、点赞、收藏!感谢支持,祝大家祉猷并茂,顺遂无虞!版本一:基础日志代码在设计C++日志系统时,我们需要考虑以下几个关键点:易用性:日志系统应该易于使用,开发者应该能够轻松地添加日志条目。性能:日志系统应......
  • 《 C++ 点滴漫谈: 十三 》C++ 中的虚拟函数革命:virtual、override 和 final 如何改变你
    摘要这篇博客深入探讨了C++中virtual、override和final关键字的核心概念与使用技巧。我们从虚函数和多态的基本概念出发,讲解了如何通过virtual实现动态绑定,使程序能够在运行时根据对象类型调用适当的函数。接着,我们深入分析了override的使用,帮助开发者避免重写错......
  • C++大内存分配错误
    支持一对一答疑的购买网址C++无法分配大内存当影像较大时,m和n是int类型时,char*a=newchar[m*n]可能出现无法分配内存的错误原因分析:由于早期数据处理需求对内存需要较小,例如早期影像较小,影像长宽的积较小,char*a=newchar[m*n]不会出错。时代变化,影像体积变大,老代码仍旧使......
  • c++入门
    ⦁C++基础1.数据类型主要有五类数据类型:布尔类型,字符型,整型,浮点型和无类型。部分数据类型及所占位数:数据类型C++语言表示所占位数范围字符型char8b(1字节)-128~127或0~255无符号字符型unsignedchar8b(1字节)0~255整型int......
  • 只谈C++11新特性 - 删除函数
    删除函数背景在C++11之前,C++的类默认会生成拷贝构造函数和赋值运算符。这在某些情况下会引发问题,尤其是在我们希望明确禁止某些操作时。假设我们有一个类,它不希望被拷贝,但未明确声明拷贝构造函数和赋值运算符,这时编译器会自动生成默认实现,导致程序员可能无意间拷贝了该......
  • 期末复习c++时 发现以前没注意的点
    期末复习因为没有往年卷做现在闲得无聊导致的......
  • 13C++循环结构-for循环(3)
    一、回文数问题:“地满红花红满地,天连碧水碧连天”是一副回文联,用回文形式写成的对联,既可以顺读,也可以倒读,意思不变。在数学中也存在这样特征的一类数,称为回文数。设n是一任意自然数,将n各个数位上的数字反向排列所得自然数m,若m等于n,则n为回文数。例如,1234321是回文数,1234567不是......
  • C#使用Tesseract C++ API过程记录
    TesseractTesseract是一个开源的光学字符识别(OCR)引擎,最初由Hewlett-Packard(惠普)实验室开发,后来由Google收购并继续维护和开源贡献。Tesseract可以识别多种语言的文字,广泛应用于将图片或扫描文档中的文本内容转换成可编辑的文本格式。随着深度学习技术的发展,Tesseract......
  • C#使用Tesseract C++ API过程记录
    TesseractTesseract是一个开源的光学字符识别(OCR)引擎,最初由Hewlett-Packard(惠普)实验室开发,后来由Google收购并继续维护和开源贡献。Tesseract可以识别多种语言的文字,广泛应用于将图片或扫描文档中的文本内容转换成可编辑的文本格式。随着深度学习技术的发展,Tesseract也整合......
  • C#调用C++代码,以OpenCV为例
    前言使用C#调用C++代码是一个很常见的需求,因此本文以知名的C++机器视觉库OpenCV为例,说明在C#中如何通过使用P/Invoke(平台调用)来调用C++代码。只是以OpenCV为例,实际上在C#中使用OpenCV可以使用OpenCVSharp这个项目,这是一个很优秀的项目,GitHub地址:https://github.com/shimat/opencv......