引用在模板推导中的基础逻辑

reference

引用是C++相对于C语言指针引入的一个新语法,可以以简单变量来使用指针。这种语法在使用的时候还是比较方便的,但是也在模板类型推导的过程中也带来了一些需要额外关注的细节。

例子

下面的例子中,rt是一个引用类型,问题是在模板参数函数Harry的定义中,模板参数TSECER并没有包含引用类型,那么此时以rt为参数调用时,模板函数中的Fry参数是什么类型呢,或者更详细的说:引用会被传递给模板吗?

tsecer@harry: cat cpp.tmpl.type.deduction.cpp 
struct tsecer
{};

template<typename TSECER>
void Harry(const TSECER Fry)
{}

void Leela()
{
    tsecer t, &rt = t, *pt = &t;
    Harry(rt);
    Harry(pt);
}

资料

在网络上有关于该问题的一个讨论,答案也是简单明了:如果调用的地方是一个引用表达式,那么此时引用会先被剥离。

If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T.

该答案引用了c++语法中的规定

If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.
[Note 1: Before the lifetime of the reference has started or after it has ended, the behavior is undefined (see [basic.life]). — end note]

大致的意思是,在调用

Harry(rt);

时,此时rt是一个引用类型的表达式(expression),C++规定引用类型表达式在推导的时候会先去掉引用,所以rt作为参数的时候,匹配的argument是单独的tsecer类型—没有引用。所以,此时调用的时候会生成一个临时变量给Harry函数调用时使用,对应的模板函数也是单独的const TSECER类型而没有引用。

模板

前面说调用处类型是引用的话会先去掉引用,对应地,在模板参数定义中的引用也会做相应的处理,这个相对前一个规则来说是一个比较明确的位置。文档中这么说明

If P is a reference type, the referenced type is used for deduction.

这个感觉和调用处的说明相呼应:调用处表达式的引用会被剔除,而模板parameter中也只是使用引用的类型来进行匹配,这样其实两个部分是对应/对等的。或者说:引用不作为类型属性来进行模板匹配

这里再补充说明下,前面说的都是参数推导而不是参数替换(substitute)。当推导完成、参数替换之后,模板定义中就会有引用出现了。以下面代码为例

struct tsecer
{};

template<typename TSECER>
void Harry(const TSECER &Fry)
{}

void Leela()
{
    tsecer t, &rt = t;
    Harry(rt);
}

在调用处,rt先剥夺引用类型,成为简单的tsecer类型;在模板函数中,先去掉const(如果有volatile也会被剥离),然后去掉引用,此时参数中就是一个简单的TSECER类型;这样模板的parameter TSECER和调用处的argument tsecer直接简单匹配,推导出此次调用的模板parameter就是tsecer。最后将这个类型带入到Harry函数(参数替换),Harry就是一个const tsecer &类型为参数的函数了。

gcc

argument

/* In all nodes that are expressions, this is the data type of the expression.
   In POINTER_TYPE nodes, this is the type that the pointer points to.
   In ARRAY_TYPE nodes, this is the type of the elements.
   In VECTOR_TYPE nodes, this is the type of the elements.  */
#define TREE_TYPE(NODE) \
(CONTAINS_STRUCT_CHECK (NODE, TS_TYPED)->typed.type)

/* Like is_bitfield_with_lowered_type, except that if EXP is not a
   bitfield with a lowered type, the type of EXP is returned, rather
   than NULL_TREE.  */

tree
unlowered_expr_type (const_tree exp)
{
  tree type;
  tree etype = TREE_TYPE (exp);

  type = is_bitfield_expr_with_lowered_type (exp);
  if (type)
    type = cp_build_qualified_type (type, cp_type_quals (etype));
  else
    type = etype;

  return type;
}

对于引用类型,在gcc内部是

/* C unary `*' or Pascal `^'.  One operand, an expression for a pointer.  */
DEFTREECODE (INDIRECT_REF, "indirect_ref", tcc_reference, 1)

类型结构,也就是引用在gcc内部就是 ptr这种形式表达了,只是在语言层面屏蔽了指针的dereference。或者说,Harry(rt)和Harry(pt)在gcc看来是一样的。

在访问引用类型标识符时,编译器会自动把标识符转换为间接引用类型

/* We are using a reference VAL for its value. Bash that reference all the
   way down to its lowest form.  */

tree
convert_from_reference (tree val)
{
  if (TREE_TYPE (val)
      && TREE_CODE (TREE_TYPE (val)) == REFERENCE_TYPE)
    {
      tree t = TREE_TYPE (TREE_TYPE (val));
      tree ref = build1 (INDIRECT_REF, t, val);

      mark_exp_read (val);
       /* We *must* set TREE_READONLY when dereferencing a pointer to const,
	  so that we get the proper error message if the result is used
	  to assign to.  Also, &* is supposed to be a no-op.  */
      TREE_READONLY (ref) = CP_TYPE_CONST_P (t);
      TREE_THIS_VOLATILE (ref) = CP_TYPE_VOLATILE_P (t);
      TREE_SIDE_EFFECTS (ref)
	= (TREE_THIS_VOLATILE (ref) || TREE_SIDE_EFFECTS (val));
      val = ref;
    }

  return val;
}

而在build1_stat函数中,也是把引用的类型赋值给了TREE_TYPE,对应代码

TREE_TYPE (t) = type;

也就是说,unlowered_expr_type对于变量引用也同样返回的引用的类型。

tree
build1_stat (enum tree_code code, tree type, tree node MEM_STAT_DECL)
{
  int length = sizeof (struct tree_exp);
  tree t;

  record_node_allocation_statistics (code, length);

  gcc_assert (TREE_CODE_LENGTH (code) == 1);

  t = ggc_alloc_tree_node_stat (length PASS_MEM_STAT);

  memset (t, 0, sizeof (struct tree_common));

  TREE_SET_CODE (t, code);

  TREE_TYPE (t) = type;
///...
}

parameter

/* Adjust types before performing type deduction, as described in
   [temp.deduct.call] and [temp.deduct.conv].  The rules in these two
   sections are symmetric.  PARM is the type of a function parameter
   or the return type of the conversion function.  ARG is the type of
   the argument passed to the call, or the type of the value
   initialized with the result of the conversion function.
   ARG_EXPR is the original argument expression, which may be null.  */

static int
maybe_adjust_types_for_deduction (unification_kind_t strict,
				  tree* parm,
				  tree* arg,
				  tree arg_expr)
{
///...
  /* [temp.deduct.call]

     If P is a cv-qualified type, the top level cv-qualifiers
     of P's type are ignored for type deduction.  If P is a
     reference type, the type referred to by P is used for
     type deduction.  */
  *parm = TYPE_MAIN_VARIANT (*parm);
  if (TREE_CODE (*parm) == REFERENCE_TYPE)
    {
      *parm = TREE_TYPE (*parm);
      result |= UNIFY_ALLOW_OUTER_MORE_CV_QUAL;
    }
///...
}

栗子

前面说的都是“顶层”类型说明,在非顶层结构中,依然是要严格匹配const、reference等类型的。

在下面的例子中,foo函数参数有引用,所以能够匹配成功;而bar函数的int没有引用,导致匹配失败。

tsecer@harry: cat -n  ref.in.func.arg.cpp    
     1  int foo(int &);
     2  int bar(int);
     3
     4  template<typename T>
     5  void baz(int(*)(T &))
     6  {
     7      T t;
     8  }
     9
    10  int tsecer()
    11  {
    12      baz(foo);
    13      baz(bar);
    14  }
    15
tsecer@harry: gcc -c ref.in.func.arg.cpp  
ref.in.func.arg.cpp: In function 'int tsecer()':
ref.in.func.arg.cpp:13:12: error: no matching function for call to 'baz(int (&)(int))'
     baz(bar);
            ^
ref.in.func.arg.cpp:5:6: note: candidate: template<class T> void baz(int (*)(T&))
 void baz(int(*)(T &))
      ^~~
ref.in.func.arg.cpp:5:6: note:   template argument deduction/substitution failed:
ref.in.func.arg.cpp:13:12: note:   mismatched types 'T&' and 'int'
     baz(bar);
            ^
tsecer@harry: 

posted on 2023-05-12 19:35  tsecer  阅读(39)  评论(0编辑  收藏  举报

导航