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

一、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

你可能感兴趣的:(C++11,C++,c++)