c++ 模板参数有默认值时模板特例化匹配问题

用struct 当示例,与class一样。只是省得写public。struct默认是public的。

模板分为主模板(原型模板,primary template),还有一些偏特化或者全特化。

模板参数有两种类型:一个是类型 typename;另外是类型固定,是取值如 int A。

模板的声明类似函数的形式参数:1号template<typename T, typename U = int> vs  f(int, float=1.0)

  • 全特化:就是给参数全部赋值

template<> struct  S<bool, float>; template里面是空的,说明没有模板参数了,就是全部都特化,赋值了。赋值实例化是另外回事。这里是模板定义。不是实例化用它!!!

             当然,由于第二个参数有默认值, 这个定义template<> struct S<bool>; 就成了 S<bool, int>。说明偏特化定义时,是可以在bind参数时省略默认值的。

另外参数T定义了必须用在模板参数赋值上。否则报下面错误:

  • 偏特化(部分特化):就是类似函数的bind,绑定了一些参数,其他还待绑定。(全特化就是全部绑定了)

             比如:2号 template<typename T> struct S<true,T>; 这个template里面有一个参数,说明有一个模板参数还没有绑定。就是部分特化。

             而且注意这个部分特化,需要待定的参数一定是原型的第二个模板参数。

     偏特化可能还会模板参数变多:template<typename T,typename E,typename E2> struct S<true,T>{ E except; E2 e2;}

     上面例子就是模板参数变多了,但是对原型的引用是不变的S<true,T>。

如下的源码:

template<typename T, typename U = int>  //这个是相当于函数的接口原型。即模板的接口原型
                                        //U参数如果被省略的话,默认值是int。
class S{ //#1
  public:
    void f1(){};
};

template<>
class S<void> {		//#2  这个是个全特化,等于函数赋予了实参,等于给接口 S<T,U=int> 的参数赋值为 S<void>,
                    // 因为默认值是int,所以最终为
                    //S<void, int> 这个特例化就是给接口全部赋上实参。
  public:
    void f2(){}; //3.
};

int main()
{
  S<void, int> sv;	// OK: uses #2, definition available  #3
    // S<void, int> 这个去优先匹配特例化,发现匹配,所以就用了 #2。
  //sv.f1();//error
  sv.f2();//ok
}

clang模板展开时,#3变成:

 S<void> sv = S<void>();

为啥S<void, int> 不走主模板分支 #1?

因为 S<void> 去看接口原型是 S<T,U=int>。所以 S<void>就是 S<void, int>。

其实 #2 的实现,S<void> 就是 S<void,int>。

实现过程:

class S<void> 的实例化,查找到前面有主模板定义:

template<typename T, typename U = int> class S

因为第二个模板参数默认值是int所以 会自动生成

S<void, int>

也就是说,class S<void> 根据主模板,就是 S<void, int>。

接下来就是 S<void, int> 选择哪个匹配问题。根据模板匹配找最适合的,范围更小的。最终选定偏特化的,主模板的范围太广了。

再看一个小的示例:

//这个是主模板
template<bool, typename T= void> //这个T,可以省略,因为没有被引用 即:template<bool, typename=void>
struct en_if {};

//一个偏特化模板。之所以不是全特化,还保留了主模板的第二个参数
template< typename T>
struct en_if <true, T> { using TYPE = T; };

/////------
en_if<true>::TYPE* a;
static_assert(std::is_same_v<en_if<true>::TYPE, void>);

en_if<true>:它就是个参数实例化,参数赋值,肯定得符合原型:

第一步,看模板原型名字,参数都一样的:第一个有隐含值,可以选,成为 template<true, void>。

第二步,决策哪个适合,第二个偏序的更范围小。优先选。

 

这是个全特化,因为没有 templace<xx>存在了,那就是 templace<>全特化了。  

两个都符合:

  • 选主模板:<true> 推出 <bool, T=void> 即 <true,void>
  • 选偏特化模板:<true> 推出 <true ,T> 即<true,true>

上面选哪个?上面分析错误。true是个值,不是类型。而第二个模板参数是 typename T。所以导出 <true,true>是有问题的。所以选中了第一个模板。en_if<true>永远不可能选中第二个模板,因为这个true是模板原型<bool,typename T>的第二个参数,而第二个是类型。true是bool值,不能赋值给类型。

首先会匹配偏特化,发现可以匹配,只是少了个参数。再根据主模板接口定义 <bool, T=void>,少的参数有默认值void。最终形成: en_if<true, void>。

接下来 en_if<true, void>选择主模板,还是偏特化的问题。

主模板:template<bool,typename T=void>

偏特化:en_if<true, void>

偏特化更符合,范围更小。即符合偏特化,肯定符合主模板;符合主模板,不一定符合偏特化。

偏特化被改的话,都会报错:

//一个偏特化模板。之所以不是全特化,还保留了主模板的第二个参数
template< typename T=void>
struct en_if <true, T> { using TYPE = T; };

//一个偏特化模板。之所以不是全特化,还保留了主模板的第二个参数
template< typename T=int>
struct en_if <true, T> { using TYPE = T; };

上面两种修改都会导致报错:这是由于默认值只能出现在模板原型中。偏特化不能定义默认值。

error: default template argument in a class template partial specialization
template< typename T=void>
^
1 error generated.
en_if<true> 就不可能选第二个模板。第二个模板的参数是个类型,template< typename T=int>, 而true是值,
那就必须选第一个模板<bool, typeface T>.

实际应用

通过默认模板参数 来选择默认的特化版本,当条件不成立时,退回到主模板。
  • 当默认模板参数是类型,则一般为void,并在特化版本通过void_t或者 enable_if_t选择;
  • 若为值,则用bool常量表达式结果作为默认参数并在特化版本中通过true false选择。

以上摘自《c++20高级编程》罗能 p64.

enable_if 作为约束使用,它虽然出现在模板上的一个参数,但是没有用,只是作为模板模式匹配用途。在c++20用约束来替换。

1, 关于默认值的模式匹配

template<bool B, class T=long>
struct enable_if{};

template<class T>
struct enable_if<true,T>{
  using type = T;
};

enable_if<true>::type a;

可以看到,a是个 long 类型。

template<bool B, class T = long>
struct enable_if
{
};

/* First instantiated from: insights.cpp:9 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct enable_if<true, long>
{
  using type = long;
};

#endif


template<class T>
struct enable_if<true, T>
{
  using type = T;
};

enable_if<true>::type a;

 2,它真正用途是作为 选择器。上面的long其实没用,用void代替。

#include <iostream>

template<bool B, class T=void>
struct enable_if{};

template<class T>
struct enable_if<true,T>{
  using type = T;
};

template<class T, class U=typename enable_if< std::is_integral_v<T> >::type >
struct X{
};

X<int> ok;
X<std::string> compile_failed;

当enable_if<true>时,才会匹配上带type类型的偏序模板。由于enable_if<false> 会匹配主模板定义,而里面是没有 type 这个定义的。所以导致 compile_failed。

可以看到上面的 class U模板参数只是作为约束存在,没有任何实际意义。c++20用 concept。

#include <iostream>

template<class T>
  concept IntType= std::is_integral_v<T>;


template<IntType T>
struct X{
};

X<int> ok;
//X<std::string> compile_failed;

进一步可以简化为:

template<class T>
requires std::is_integral_v<T>
struct X{
};

X<int> ok;

 

posted @ 2022-12-24 17:56  Bigben  阅读(545)  评论(0编辑  收藏  举报