C++点点滴滴(上)

Author Avatar
Magicmanoooo 3月 09, 2019
  • 在其它设备中阅读本文章

operator priority

_Bool类型

C99开始引入,其定义在头文件中。其有两个值:0代表false和1代表true.
【注意】:_Bool类型的转换与其他integer类型的转换不同。

例如:(bool)0.5的值为1,而(int)0.5的值为0

在gcc中,这个头文件的源码如下:

/* Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc.
 */
#ifndef _STDBOOL_H
#define _STDBOOL_H
#ifndef __cplusplus
#define bool    _Bool
#define true    1
#define false    0
#else
/* __cplusplus ,应用于C++里,这里不用处理它*/
/* Supporting <stdbool.h> in C++ is a GCC extension.  */
#define _Bool    bool
#definebool    bool
#define false    false
#define true    true
#endif
/* __cplusplus */
/* Signal that all the definitions are present.  */
#define __bool_true_false_are_defined    1
#endif
/* stdbool.h */

可见,中定义了4个宏,booltruefalse__bool_true_false_are_defined。 其中bool就是 _Bool类型,truefalse的值为10__bool_true_false_are_defined的值为1

arithmetic types(算数类型)

  • Boolean type(布尔类型)
  • Character types(字符类型)
    • signed char
    • unsigned char
    • char (注意:char不同于signed char和unsigned char)
      【注意】:在标注库中还定义了宽字符的typedef name:wchar_tchar16_tchar32_t
  • Integer types(整数类型)
    • short int
    • unsigned short int
    • int
    • unsigned int
    • long int
    • unsigned long int
    • long long int
    • unsigned long long int

  • Real floating types(浮点数)
    • float
    • double
    • long double

C语言中的types

  • void
  • basic type(基本类型)
    • char
    • signed integer types(有符号整形)
      1. standard(标准型)
        • signed char
        • short
        • int
        • long
        • long long(since C99)
      2. extended(扩展型)
        • implement—defined(由编译器实现),例如_int128
    • unsigned integer types(无符号整形)
      1. standard(标准型)
        • _Bool
        • unsigned char
        • unsigned short
        • unsigned int
        • unsigned long
        • unsigned long long
      2. extended(扩展型)
        • implement—defined(由编译器实现),例如_int128
    • floating types(浮点类型)
      • real floating types
        • float
        • double
        • long double
      • complex types and imaginary types
  • enumerated types(枚举类型)
  • derived types(派生类型)
    • array types
    • structure types
    • union types
    • function types
    • pointer types
    • atomic types

type groups(类型分组)

  • object types(对象类型):除了function types以外的所有类型
  • character types
    • char
    • signed char
    • unsigned char
  • integer types
    • char
    • signed integer types
    • unsigned integer types
    • enumerated types
  • real types
    • integer types
    • real floating types
  • arithmetic types
    • integer types
    • floating types
  • scalar types(标量类型)
    • arithmetic types
    • pointer types
  • aggregate types(聚合类型)
    • array types
    • structure type(union和struct)
  • derived types
    • array types
    • function types
    • pointer

raw sting literal

这是一种比较特殊的string,它的用途是:在使用的过程中,可以忽略转义字符(escape character,例如\n,\t,\"),即将转义字符当做普通的字符进行处理。其语法为:R"(xxxx)";

例子:

std::string normal_string = "I'm a normal string.\nI'm also a normal string\n";
std::string raw_string = R"(I'm a raw string.\nI'm also a raw string\n)";

输出结果为:

nullptr_t

其定义在<stddef>

typedef deltype(nullptr) nullptr_t;

表示的是null pointer literal(空指针字面值常量)nullptr的类型,它既不是指针类型,也不是指向成员的指针类型。

即:nullptr是constant,而nullptr_tnullptr的类型。

nullptrstd::nullptr_t 类型的一个 pointer literal,而且它是一个prvalue(即不能利用 & 对其进行取地址)。

  • 一个 std::nullptr_t 类型的 prvalue 是一个 null pointer constant,这种这种 integral null pointer constant 能够被转换为 std::nullptr_t,而反向转换则不允许。这允许为 pointers 和 integers 重载函数,并且通过传递 nullptr 来进行选择pointer version。而传入 NULL 或者 0 则会令人困惑地选择 int 版本。
  • nullptr_t 强制转换为 ingeral type 需要使用 reinterpret_cast,其作用等同于 (void*)0强制转换为 integral type。reinterpret_cast不能将nullptr_t转换为任何pointer type。如果可能,应使用隐式转换或者static_cast
  • 标准规定:sizeof(nullptr_t)等同于sizeof(void*)

【注】:
尽管NULL的定义#define NULL 0;,但其并不保证其值就是0,也可以是0L,在进行调用void f(int); void f(char *);中将会产生歧义,而使用nullpter则只会选择pointer version(从不会选择int这一重载)。此外,nullpter还可以转换为bool

nullptr的简单实现:

const class nullptr_t {
public:
    template<class T>
    inline operator T*() const { 
        return 0; 
    }

    template<class C, class T>
    inline operator T C::*() const { 
        return 0; 
    }
private:
    void operator&() const;
} nullptr = {};

nullptr底层实现原理

为什么0NULL可以直接赋给指针

#if !defined(NULL) && defined(__NEEDS_NULL)
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

因为0是 null pointer constant ,NULL宏也会定义为一个 null pointer constant , null pointer constant 可以用来给指针初始化和赋值是标准钦点的,结果是指针的值变为 null pointer value 【这并不代表值变为0!】,注意C++中0只有作为整数字面量的时候才是 null pointer constant,C的要求可能和C++不完全相同。

intptr_t

C99引入,使用int时,可以使用其来保证平台的通用性。它在不同的平台上编译时长度不同,integer type capable of holding a pointer。
其定义为:

#if  _WORDSIZE==64
  typedef long int intptr_t
#else
  typedef int intptr_t
#endif

使用inptr_t的场景:

  • void*不能进行bitwise operation,但可以对intptr_t进行。
  • 在需要对一个地址进行bitwise operation的场景下,可以使用intptr_t

【注】:但对于bitwise operation,最好还是使用unsigned版本的uintptr_t

intptr_t替代void*作为)通用存储(用于存储指针和整数值)并不是一个好主意。因为intptr_t不保证存在,即:首先,它是在C99才引入的。其次,并不需要足够大的整数类型来保证转换之后的指针值并且不会丢失信息。

int转换为intptr_t不太可能丢失精度,但实际上并没有任何保证说明intptr_tint更宽。如果要存储指针值,请将它们存储在指针对象中。任何只想对象 或者不完整对象的指针都可转换为void*,并且在返回时不会丢失信息。然而对于指向函数的指针却没有这样的保证(但任何指向函数的指针都可以转换为任意的指向函数类型的指针且不丢失信息)。

右值引用

右值引用的解释

std::movestd::forward的差别:
std::move是无条件的转为右值引用,而std::forward是有条件的转为右值引用,更准确的说叫做Perfect forwarding(完美转发),而std::forward里面蕴含着的条件则是Reference Collapsing(引用折叠)。

template <typename T>
typename remove_reference<T>::type&& move(T&& arg) {
  return static_cast<typename remove_reference<T>::type&&>(arg);
}

【注】
std::move只是将参数转换为引用而已,它从来不会移动什么。真正的移动操作是在移动构造函数或者移动赋值操作符中发生的:

T (T&& rhs);
T& operator= (T&&rhs);

std::move的作用:只是为了让调用构造函数的时候告诉编译器去选择移动构造函数

移动

让编译器可以把费事的拷贝操作变成开销很小的移动操作。如果你有一个对象,需要移动到另一个对象,但是类中的数据都是不变的且源对象将不会再使用,那么为什么不直接让目标使用源的子数据的内存而要拷贝一份呢?移动语义就是提供了这种操作的语法,就像拷贝构造函数和拷贝赋值操作符让你能够拷贝对象一样,移动构造函数和移动赋值操作符让你能够移动一个对象。利用移动机制你还可以创造只能移动不能拷贝的类,比如std::unique_ptr

完美转发

允许你在函数模板中,接受不确定的参数,然后将它们转发给其他函数。完美转发保证在转发的过程中传递的参数的类型和传入时候是完全相同的。

std::movestd::forward仅仅是执行类型转换的函数而已。std::move无条件地将参数转换为右值引用,而std::forward仅当满足一定条件的时候将参数转换为右值引用。

转发:某些函数需要将其中一个或多个参数连同类型不变地转发给其他函数,在这种情况下,需要保持被转发实参的所有性质,包括实参类型是否为const的以及实参是左值还是右值。

现编写一个函数,它接受一个可调用表达式和两个额外的参数。我们的函数将调用给定的可调用对象,将两个额外参数逆序传递给它。

//接受一个可调用对象和另外两个参数模板
//对“翻转”的参数调用给定的可调用对象
//flip1是一个不完整的实现:top-level const和引用丢失了
template <typename F, typename T1, typename T2>
void flip1(F f, T1 t1, T2 t2) {
    f(t2, t1);
}

这个函数在一般情况下可以完美运行,但当我们希望用它调用一个接受引用参数的函数就会出现问题:

void f(int v1, int& v2) {
 std::cout<<v1<<" "<<++v2<<std::endl;
}

在这段代码找中,f改变了绑定到v2的实参的值。但是,如果我们通过flip1调用ff所做的改变就不会影响实现:

f(42, i);
flip1(f, j, 42);

问题在于j被传递给flip1的参数t1。此参数是一个普通的、非引用的类型int,而非int&。因此,这个flip1会被实例化为:
void flip1(void(*fcn)(int, int&), int t1, int t2);
j的值被拷贝到t1中。f中的引用参数被绑定到t1,而非j,从而其改变不会影响j

为了通过翻转函数传递一个引用,需要重写函数,使其参数能够保持给定实参的“左值性”。此外,还希望保持参数的const属性。通过将一个函数参数定义为一个指向末班类型参数的右值引用,可以保持其对应实参的所有类型信息。而使用引用参数(无论左值还是右值)可以保持const属性,因为在引用类型中的是low-level const。如果将函数参数定义为T1&&T2&&,通过引用折叠,便可以保持翻转实参的左值/右值属性。

template <typename F, typename T1, typename T2>
void flip2( F f, T1&& t1, T2&& t2){
    f(t2,t1);
}

与之前的版本一样,如果调用flip2(f,j,42),将传递给参数t1一个左值j。但是,在flip2中,推断出T1的类型是int&,这意味着t1的类型会折叠为int&。由于是引用类型,t1被绑定到j上。当flip2调用f时,f中的引用参数v2被绑定到t1,也就是被绑定到j。当f递增到v2时,它也同时改变了j的值。但这个版本的flip2解决了一般的结果。虽然它对于一个接受左值引用的函数可以很好地工作,但不能用于接受右值引用参数的函数。例如:

void f(int&& i, int& j) {
    std::cout<< i <<" "<<j <<std::endl;
}

如果试图通过flip2调用g,则参数t2将被传递给g的右值引用参数。即使传递一个右值flip2:

flip2(g,i,42);  //错误,不能从一个左值实例化int&&

传递给g的将是flip2中名为t2的参数。函数参数与其他任何变量一样,都是左值表达式。因此,flip2中对g的调用将传递给g的右值引用参数一个左值。

此时,可以使用forward再次重写翻转函数:

template <typename F, typename T1, typename T2>
void flip(F f, T1&& t1, T2&& t2) {
    f(std::forward<T2>(t2), std::forward<T1>(t2));
}

【注】:如果一个函数参数是指向模板类型参数的右值引用,它对应的实参的const属性和左值/右值属性将得到保持。当用于一个指向模板参数类型的右值引用函数参数时,forward会保持实参类型的所有细节。std::move只影响调用函数的选择,它本身不需要任何生成机器码

sizeof的编译器实现

sizeof运算符中的内容会被编译器直接替换,即使是汇编代码也只能看到一个constant。Clang中sizeof的处理大致为以下。

Clang的实现在lib/AST/ExprConstant.cpp
VisitUnaryExprOrTypeTraitExpr这个方法的主要作用是对sizeofalignof或者vec_step进行求值,返回表达式的类型。

bool IntExprEvaluator::VisitUnaryExprOrTypeTraitExpr(
                                    const UnaryExprOrTypeTraitExpr *E) {
  switch(E->getKind()) {
  ... //other impl
  case UETT_SizeOf: {
    QualType SrcTy = E->getTypeOfArgument();
    // C++ [expr.sizeof]p2: "When applied to a reference or a reference type,
    //   the result is the size of the referenced type."
    if (const ReferenceType *Ref = SrcTy->getAs<ReferenceType>())
      SrcTy = Ref->getPointeeType();
    CharUnits Sizeof;
    if (!HandleSizeof(Info, E->getExprLoc(), SrcTy, Sizeof))
      return false;
    return Success(Sizeof, E);
  }
  ...  //other impl
  llvm_unreachable("unknown expr/type trait");
}

其中的主要方法是HandleSizeof,结果会存放在结构为CharUnitsSizeof之中,而CharUnits为Clang内部的一个数据结构,其定义为:

/// CharUnits - This is an opaque type for sizes expressed in character units.
/// Instances of this type represent a quantity as a multiple of the size
/// of the standard C type, char, on the target architecture. As an opaque
/// type, CharUnits protects you from accidentally combining operations on
/// quantities in bit units and character units.
///
/// In both C and C++, an object of type ‘char’, ‘signed char’, or ‘unsigned
/// char’ occupies exactly one byte, so ‘character unit’ and ‘byte’ refer to
/// the same quantity of storage. However, we use the term ‘character unit’
/// rather than ‘byte’ to avoid an implication that a character unit is
/// exactly 8 bits.
///
/// For portability, never assume that a target character is 8 bits wide. Use
/// CharUnit values wherever you calculate sizes, offsets, or alignments
/// in character units.

HandleSizeof方法主要用于以char units为单位,获取给定类型的size。

static bool HandleSizeof(EvalInfo &Info, SourceLocation Loc,
                         QualType Type, CharUnits &Size) {
  // sizeof(void), __alignof__(void), sizeof(function) = 1 as a gcc extension.
  if (Type->isVoidType() || Type->isFunctionType()) {
    Size = CharUnits::One();
    return true;
  }
  if (Type->isDependentType()) {
    Info.FFDiag(Loc);
    return false;
  }
  if (!Type->isConstantSizeType()) {
    // sizeof(vla) is not a constantexpr: C99 6.5.3.4p2.
    // FIXME: Better diagnostic.
    Info.FFDiag(Loc);
    return false;
  }
  Size = Info.Ctx.getTypeSizeInChars(Type);
  return true;
}

在上述代码中,可以得知为什么sizeof中的内容会被替代:如果给定的类型为void或者function type,则编译器会直接将其替换为CharUnits::One()常量(即一个char的大小),所以在进行汇编的时候只能看见常量(汇编属于code generation的工作,而这里的代码发生在code generation之前)。此外,还需要判断Type是否为isConstantSizeType,因为需要在编译器计算出来。最后将Type传递给getTypeSizeInChars方法。

getTypeSizeInChars方法主要用以返回specified type的大小,这个方法对于imcomplete type不起作用。

CharUnits ASTContext::getTypeSizeInChars(const Type *T) const {
  return getTypeInfoInChars(T).first;
}

对于getTypeInfoInChars方法,

std::pair<CharUnits, CharUnits>
ASTContext::getTypeInfoInChars(QualType T) const {
  return getTypeInfoInChars(T.getTypePtr());
}

由于这个方法返回一个std::pair,所以在前面的调用中会有有first这个东西。但发现这个方法调用的还是getTypeInfoInChars,但参数是一个TypePointer,重载方法为:

std::pair<CharUnits, CharUnits>
ASTContext::getTypeInfoInChars(const Type *T) const {
  if (const auto *CAT = dyn_cast<ConstantArrayType>(T))
    return getConstantArrayInfoInChars(*this, CAT);
  TypeInfo Info = getTypeInfo(T);
  return std::make_pair(toCharUnitsFromBits(Info.Width),
                        toCharUnitsFromBits(Info.Align));

随后转到getTypeInfo这个方法:

TypeInfo ASTContext::getTypeInfo(const Type *T) const {
  TypeInfoMap::iterator I = MemoizedTypeInfo.find(T);
  if (I != MemoizedTypeInfo.end())
    return I->second;
  // This call can invalidate MemoizedTypeInfo[T], so we need a second lookup.
  TypeInfo TI = getTypeInfoImpl(T);
  MemoizedTypeInfo[T] = TI;
  return TI;
}

Implementing class template member functions

Implementing class template member functions和普通的class member functions不同。class template member functions的声明与定义需要在同一个头文件之中。

例子:

//B.H
template <class t>
class b
{
public:
    b() ;
    ~b() ;
} ;
// B.CPP
#include "B.H"
template <class t>
b<t>::b()
{
}
template <class t>
b<t>::~b()
{
}
//MAIN.CPP
#include "B.H"
void main()
{
     b<int> bi ;
     b <float> bf ;
}

当编译B.cpp的时候,编译器能够同时看到声明与定义,在这时,编译器不需要为template classes生成任何定义,因为在目前为止还没有产生实例(instantiation)。当编译器编译main.cpp的时候有两个实例:模板类B<int>B<float>。这个时候编译器拥有声明却没有定义,所以会报错。

【注】:c++标准里规定:a function template shall be defined in every translation unit in which it is implicitly instantiated。

pragma once

#pragma one是一个非标准单被广泛支持的预编译指令,用于说明在一次编译的过程中,当前的源文件只会被包含一次。

例子:

//file "grandparent.h"
#pragma once
struct foo {
    int member;
};

//file "parent.h"
#include "grandparent.h"

//file "child.c"
#include "grandparent.h"
#include "parent.h"

在此例中,grandparent.h被同时包含在parent.hchild.h中,这将导致编译错误,因为具有给定名称的struct只能在编译中定义一次。而#pragma once指令通过忽略grandparent.h的subsequent inclusions来避免这一问题。

大端和小端问题

  • 把指针类型reinterpret_cast会有字节序问题。如:
     int a=0x12345678;
     char* c=reinterpret_cast<char*>(&a);
     printf("%x %x %x %x\n", c[0], c[1], c[2], c[3]);
     // 小端输出: 78 56 34 12
     // 大端输出: 12 34 56 78 
    
  • 使用union做转换
    union {
        int a;
        char c[4];
     }u;
     u.a=0x12345678;
     printf("%x %x %x %x\n", c[0], c[1], c[2], c[3]);
     // 小端输出: 12 34 56 78
     // 大端输出:  78 56 34 12
    
  • 涉及读写内存外的数据,例如文件、文件流等。

    空类对象占一个字节的原因

    因为BS大大当年说要确保两个不一样的对象拥有不同的地址—“To ensure that the addresses of two different objects will be different.” 就用那1byte来在内存中占用不同地址了。:)又因为这些空class没有赋值的意义,所以就没必要占用更多内存。

    typeid

    例如:
    int i=1;
    const char* name=typeid(i).name();
    
    上述的typeid(i)不需要做任何runtime动作,而仅仅是compile time时的行为——它使用变量i的静态类型,便可以知道这是对int类型做typeid运算,最后可以直接找出int对应的std::type_info对象返回。

    If expression is not a glvalue expression of polymorphic type, typeid does not evaluate the expression, and the std::type_info object it identifies represents the static type of the expression. Lvalue-to-rvalue, array-to-pointer, or function-to-pointer conversions are not performed.

此时的typeid运算符跟sizeof运算符的大部分情况一致,只需要编译器算出表达式的静态类型就足矣。计算出表达式的静态类型是C++编译器的基本功能,类型检查、类型推导等功能都依赖它。

typeid运算符应用在一个指向多态类型对象的指针上时,typeid的实现才需要runtime行为。

If expression is a glvalue expression that identifies an object of a polymorphic type (that is, a class that declares or inherits at least one virtual function), the typeid expression evaluates the expression and then refers to the std::type_info object that represents the dynamic type of the expression. If the glvalue expression is obtained by applying the unary * operator to a pointer and the pointer is a null pointer value, an exception of type std::bad_typeid or a type derived from std::bad_typeid is thrown.

实现的时候,通常会在class的vtable里会有个slot,保存着指向该class所对应的std::type_info对象的指针。

在Clang中,前段Parser会调用ang/lib/Parse/ParseExprCXX.cpp中的ParseCXXTypeid方法,主要用于识别typeid关键字、括号等信息。

ExprResult Parser::ParseCXXTypeid() {
  assert(Tok.is(tok::kw_typeid) && "Not 'typeid'!");
  SourceLocation OpLoc = ConsumeToken();
  SourceLocation LParenLoc, RParenLoc;
  BalancedDelimiterTracker T(*this, tok::l_paren);
  // typeid expressions are always parenthesized.
  if (T.expectAndConsume(diag::err_expected_lparen_after, "typeid"))
    return ExprError();
  LParenLoc = T.getOpenLocation();
  ExprResult Result;
  // C++0x [expr.typeid]p3:
  //   When typeid is applied to an expression other than an lvalue of a
  //   polymorphic class type [...] The expression is an unevaluated
  //   operand (Clause 5).
  //
  // Note that we can't tell whether the expression is an lvalue of a
  // polymorphic class type until after we've parsed the expression; we
  // speculatively assume the subexpression is unevaluated, and fix it up
  // later.
  //
  // We enter the unevaluated context before trying to determine whether we
  // have a type-id, because the tentative parse logic will try to resolve
  // names, and must treat them as unevaluated.
  EnterExpressionEvaluationContext Unevaluated(
      Actions, Sema::ExpressionEvaluationContext::Unevaluated,
      Sema::ReuseLambdaContextDecl);
  if (isTypeIdInParens()) {
    TypeResult Ty = ParseTypeName();
    // Match the ')'.
    T.consumeClose();
    RParenLoc = T.getCloseLocation();
    if (Ty.isInvalid() || RParenLoc.isInvalid())
      return ExprError();
    Result = Actions.ActOnCXXTypeid(OpLoc, LParenLoc, /*isType=*/true,
                                    Ty.get().getAsOpaquePtr(), RParenLoc);
  } else {
    Result = ParseExpression();
    // Match the ')'.
    if (Result.isInvalid())
      SkipUntil(tok::r_paren, StopAtSemi);
    else {
      T.consumeClose();
      RParenLoc = T.getCloseLocation();
      if (RParenLoc.isInvalid())
        return ExprError();
      Result = Actions.ActOnCXXTypeid(OpLoc, LParenLoc, /*isType=*/false,
                                      Result.get(), RParenLoc);
    }
  }
  return Result;
}

关键之处在于ActOnCXXTypeid方法,其会通过&PP.getIdentifierTable().get("type_info")IdentifierTable中寻找有关type_info的信息,然后也可以从这个方法中获取有关runtime多态的信息。

Clang实现运行时的动态是通过vtable里面插入的std::type_info来做的,然后在运行时通过取vtabletype_info来达到目的。

static_castreinterpret_cast的区别

POD

POD类型为plain old data,简称旧数据。POD本意是直接能与C library二进制直接交流的类型,但其中也有C++独有的类型。

POD类型的要求:

  • 标量类型(scalar type)
    • 算数(整数、浮点数)类型(arithmetic type)
    • 枚举类型(enum type)
    • 指针类型(pointer type)
    • 指向成员的指针类型(pointer-to-member type)
    • std::nullpter_t type
    • 以上类型的cv-qualified version
  • POD 类(class/structunion
    • POD struct
    • POD union
  • 以上类型的数组和 cv 限定版本

POD struct和POD union的要求:

  • 平凡类(trivial class)
  • 标准布局类(standard-layout class)
  • 没有非POD class类型或者其数组的non-static 数据成员

平凡类(trivial class)的要求:

  • 可平凡拷贝类(trivially-copyable class)
    • copy ctor、move ctor、copy assignment operator、move assigment operator均为trivial或者被删除
    • 上述特殊成员函数至少有一个没有被删除
    • 拥有trivial且未被删除的dtor
  • 所有默认ctor均为trivial或者被删除,或至少有一个未被删除

平凡默认构造函数(trivial default ctor)的要求:

  • 该ctor不是用户提供的(user-provided,即隐式定义或者显示=default
  • 类没有虚函数或者虚基类
  • 所有non-static数据成员都没有默认初始化器(initilizer)
  • 所有直接基类都有平凡默认构造函数(trivial default ctor)
  • 每个类类型成员(class type member,或者类类型数组成员的元素)拥有平凡默认构造函数

平凡析构函数(trivial dtor)的要求:

  • dtor不是用户提供(隐式定义或者显示=default
  • 所有直接基类都有trivial dtor
  • 每个类类型成员(或者类类型数组成员的元素)拥有trivial dtor

平凡复制/移动构造函数(trivial copy/move ctor)要求:

  • 该ctor不是的用户提供(隐式定义或显式=default
  • 类没有虚函数或虚基类
  • 为每个直接基类子对象选择的复制/移动构造函数为平凡(即直接基类的子对象拥有trivial copy/move ctor)
  • 为每个类类型成员(或类类型数组成员的元素)选择的复制/移动构造函数为平凡

平凡复制/移动运算符(trivial copy/move assignment operator)要求:

  • 该赋值运算符不是用户提供(隐式定义或显式=default
  • 类没有虚函数或虚基类
  • 为每个直接基类子对象选择的复制/移动赋值运算符为平凡
  • 为每个类类型成员(或类类型数组成员的元素)选择的复制/移动赋值运算符为平凡

【注意】:平凡的拷贝/移动构造函数、平凡的拷贝/移动赋值运算符的行为等价于用std::memmovestd::memcpy拷贝对象表示。平凡的默认构造函行为等价于没有操作

标准布局类型(standard layout type)的要求:

  • 没有非标准布局类型的基类
  • 没有虚函数或虚基类
  • 对于每个直接或间接基类,该基类的只有一个子对象
  • 没有非标准布局类型(或其数组)或者引用类型的non-static数据成员
  • 所有non-static数据成员拥有同一访问控制(public/protected/private
  • 该类和基类中的所有non-static数据成员、位域都在同一个类中声明

简而言之,POD类型就是同时满足标准布局类型和平凡类型的类型

其他关于POD类型的参考

定义了移动构造函数后,拷贝构造函数默认为删除的

对于Visual Studio

Visual Studio does not support defaulted move constructors or move-assignment operators as the C++11 standard mandates.

copy constructor的默认生成是为了与C兼容,当年没有这个特性的话C++就没人用了。但是很多情况下默认生成的copy constructor是会造成问题的,因此标准委员会早就想把这个特性去除,但为了向后兼容还是一直留着。而C++11里加入了move,它在历史遗留的code里是不可能出现的,不存在兼容问题,所以你定义了move constructor后copy constructor就不默认生成了。

这其实表明,标准委员会倾向的代码风格是:在需要default copy constructor 的时候手动加入= default(所谓opt-in,目前来讲就是move constructor定义后的情况),而不是在不需要default copy constructor的时候手动加= delete(所谓opt-out,目前的其他情况)

例子:
如果定义了destructor,copy constructor很可能会出问题,但历史上这时仍会生成default copy constructor。在C++11里已经把这种情况下的default copy constructor定为了“deprecated”,也就是说未来的某个版本里也许就会变成“如果定义了move constructor或destructor,default copy constructor默认为deleted”。

参考:Why user-defined move-constructor disables the implicit copy-constructor?

std::move

std::move并不会真正地移动对象,真正的移动操作是在移动构造函数、移动赋值函数等完成的,std::move只是将参数转换为右值引用而已(相当于一个static_caststd::move只影响调用函数的选择,它本身不需要任何生成机器码

std::string str = "test";
string&& r = std::move(str);

在上述代码中,只定义了一个指向str的右值引用,str并没有被移走。随后执行std::string t(r);
,需要注意的是右值引用用于表达式中时会变为左值,所以这里调用的其实是拷贝构造函数,str自然也不会被移走。如果要移走的话还要加一次std::move,比如std::string t(std::move(r));str就能被移走了。

char*(*(*a)(void))[20];

void (*a)(void)

名为a的函数指针,指向接受类型为void,返回类型也为void 的函数

char *(*a)(void)

名叫a的函数指针,指向接受类型为void,返回类型为char指针的函数

char (*(*a)(void))[20]

名为a的函数指针,指向接受类型为void,返回类型为指向长度 20 的char数组的指针的函数

char *(*(*a)(void))[20]

名叫为a的函数指针,指向接受类型为void,返回类型为指向长度 20 的char*数组的指针的函数

特化和偏特化

所谓模板特例化,即对于通例中的某种或某些情况做单独专门实现,最简单的情况是对每个模板参数指定一个具体值,这成为完全特例化(full specialization)。另外,可以限制模板参数在一个范围取值或满足一定关系等,这称为部分特例化(partial specialization)。用数学上集合的概念,通例模板参数所有可取的值组合构成全集 U,完全特例化对 U 中某个元素进行专门定义,部分特例化对 U 的某个真子集进行专门定义

需要特化或者偏特化的原因:因为编译器认为,对于特定的类型,如果你能对某一功能更好的实现,那么就该听你的

在某些情况下,通用模板的定义对特定类型并不适合。当不能使用模板版本时,可以定义类型或者函数模板的一个特例化版本。

一个特化版本就是模板的一个独立的定义,在其中一个或者多个模板参数被指定为特定的类型。特化的形式为:template <>,其中<>指出将为原模板的所有模板参数提供实参。

特化就是将模板的参数写死,针对这样一种具体的实现,告诉编译器要去生成的是这段代码,而非是将参数传入模板当种简单的替换。 偏特化就属于特化和原模板之间,有些参数被写定,有些参数还是T根据实例化的时候去替换。

例子:

template<typename T1, typename T2>  
class Test  {  
public:  
    Test(T1 i,T2 j):a(i),b(j){cout<<"模板类"<<endl;}  
private:  
    T1 a;  
    T2 b;  
};  

template<>  
class Test<int , char>  {  
public:  
    Test(int i, char j):a(i),b(j){cout<<"全特化"<<endl;}  
private:  
    int a;  
    char b;  
};  

template <typename T2>  
class Test<char, T2>  
{  
public:  
    Test(char i, T2 j):a(i),b(j){cout<<"偏特化"<<endl;}  
private:  
    char a;  
    T2 b;  
};  

Test<double , double> t1(0.1,0.2);  
Test<int , char> t2(1,'A');  
Test<char, bool> t3('A',true); 

【注】:当特化一个函数模板时,必须为原模板中的每个模板参数都提供实参。

std::declval

定义在<utility>,其原型为:

template<class T>
typename std::add_rvalue_reference<T>::type declval() noexcept;

作用是将任意类型T转换为引用类型,从而使得可以在decltype表达式中,不需要经过构造函数就能够使用member function

通常在模板中适应declval,它所接受的模板实参通常可能没有构造函数,但有相同的成员函数,并返回所需类型。

其返回值不能够被调用,因此不会返回值。返回的类型为T&&,如果Tvoid,此情况下的返回类型是T

例子:

#include <utility>
#include <iostream>

struct Default { int foo() const { return 1; } };

struct NonDefault {
    NonDefault(const NonDefault&) { }
    int foo() const { return 1; }
};

int main() {
    decltype(Default().foo()) n1 = 1;                   // type of n1 is int
//  decltype(NonDefault().foo()) n2 = n1;               // error: no default constructor
    decltype(std::declval<NonDefault>().foo()) n2 = n1; // type of n2 is int
    std::cout << "n1 = " << n1 << '\n'
              << "n2 = " << n2 << '\n';
}

扩展:Modern C++ Features – decltype and std::declval

std::void_t

C++17的新特性,在早期的C++版本中实现这个功能需要:

template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;

其原型为:
template< class ... > using void_t = void;

例子:

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

// specialized as has_member< T , void > or discarded (sfinae)
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : std::true_type { };

class A {
public:
    int member;
};

class B {
};

static_assert( has_member< A >::value , "A" );
static_assert( has_member< B >::value , "B" );

个人理解:

  1. has_member< A >
    • has_member< A , void_t< decltype( A::member ) > >
      • A::member exists
      • decltype( A::member ) is well-formed
      • void_t<> is valid and evaluates to void
    • has_member< A , void > and therefore it chooses the specialized template
    • has_member< T , void > and evaluates to true_type
  2. has_member< B >
    • has_member< B , void_t< decltype( B::member ) > >
      • B::member does not exist
      • decltype( B::member ) is ill-formed and fails silently (sfinae)
      • has_member< B , expression-sfinae > so this template is discarded
    • compiler finds has_member< B , class = void > with void as default argument
    • has_member< B > evaluates to false_type

当遇到has_member< A >::value时,编译器会去查找has_member,最终找到primary class template,即template< class , class = void > struct has_member;。此时,模板参数列表<A>会与primary template的参数列表进行比较。因为primary template拥有两个参数,但此时只提供了一个参数,则剩余的参数将会被默认为default template argument:void。这就如同书写了has_member<A, void>::value

现在,将模板参数列表与has_memeber的特化版本进行比较。仅当没有特化版本与之匹配时,primary template才会被用作后备。所以偏特化版本将会被考虑进来:

template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : std::true_type { };

编译器试图将模板参数:A, void与偏特化中定义的模式:Tvoid_t<...>进行逐个匹配。首先,进行模板参数推导。上面的偏特化仍然是一个拥有模板参数的模板,需要参数进行“填充”。

第一个模式T允许编译器推导出模板参数T。这是一个trivial deduction,但考虑到想T const&这样的模式,所以仍然需要进行推导出T。对于模式T和模板参数A,可以推导出TA

在第二种模式void_t< decltype( T::member ) >中,模板参数T出现在无法从任何模板参数推导出来的上下文(context)中,有以下两个原因:

  • decltype中的表达式将无法由模板参数显示地推导得到,因为它可能是arbitrarily complex(The expression inside decltype is explicitly excluded from template argument deduction. I guess this is because it can be arbitrarily complex.)。
  • 即使使用了像void_t <T>没有decltype的模式,在今后解析的别名模板(alias template)上还是会发生T的推导。也就是说,当解析了别名模板,然后尝试从resulting pattern中推导出类型T。然而,得到的resulting pattern是void,它不依赖与T,因此不允许找到T的一个特定类型。
  • 现在模板参数的推导便结束了,被推导出来的模板参数被替换(substituted)。这就产生了以下的特化代码:
    template<>
    struct has_member< A, void_t< decltype( A::member ) > > : true_type { };
    
    现在可以计算(evaluate)类型void_t< decltype( A::member ) > >,它被替换之后是well-formed,因此不会发生Substitution Failure。所以将得到:
    template <>
    struct has_member<A, void> : true_type { };
    
    现在将这个特化版本的模板参数列表与之前提供的原始模板参数has_member<A>::value进行比较。两种类型完全匹配,因此选择这个版本的特化。

此外,当我们定义下列模板时:

template< class , class = int > // <-- int here instead of void
struct has_member : false_type { };

template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type { };

我们最终得到了相同的特化:

template<>
struct has_member<A, void> : true_type { };

但是has_member<A>::value的模板参数列表已经变成了<A, int>。参数并与能够与特化版本的参数匹配,所以将选择primary template。

为什么需要使用void_t
如果不使用void_t,则偏特化版本的模板将永远不会被匹配上,因为decltypeT求值为非void,这就将导致不会匹配上偏特化版本,从而只能选择primary template。现在如果你知道函数最终将总是会返回一个相同类型时,你便可以随意地更改primary template的默认参数。

这就需要引入void_t,因为void_t只是一个identity type trait,
即无论什么情况下,都是产生void。这就意味着在第二个例子中,无论decltype的计算结果如何,第二个模板参数都将为void,因此如果decltype是well-formed,将匹配成功并选择这个特化版本。

Detection Idiom:is_detected

即使有了void_t,但每次需要一个新的判定时就得再重写一次SFINAE,显得有点儿不够直观。

template <typename T>
using has_type_t = typename T::type;
template <typename T>
using has_type = is_detected<has_type_t, T>;

虽然is_detected还没有进入标准,但依然可以在 C++11 中实现:

template <typename, template <typename...> class Op, typename... T>
struct is_detected_impl : std::false_type {};
template <template <typename...> class Op, typename... T>
struct is_detected_impl<void_t<Op<T...>>, Op, T...> : std::true_type {};

template <template <typename...> class Op, typename... T>
using is_detected = is_detected_impl<void, Op, T...>;

下面是用is_detected判断成员函数是否存在:

template <typename T>
using has_get_t = decltype(std::declval<T&>().get());
template <typename T>
using has_get = is_detected<has_get_t, T>;

template template parameters

需要使用template template syntax来传递一个参数,其类型是依赖于另一个template的template,如下所示:

template <template<class> class H, class S>
void f(const H<S> &value) {
}

这里,H是一个模板,但希望这个函数能够处理H的所有特化。

实际上,模板模板参数的用处非常明显。 一旦你了解到C ++ stdlib没有为标准容器类型定义流输出操作符的漏洞,便可以继续写下这样的东西:

template<typename T>
static inline std::ostream& operator<<(std::ostream& out, std::list<T> const& v) {
    out << '[';
    if (!v.empty()) {
        for (typename std::list<T>::const_iterator i = v.begin(); ;) {
            out << *i;
            if (++i == v.end())
                break;
            out << ", ";
        }
    }
    out << ']';
    return out;
}

然后你会发现,对于vector的代码也与上面的类。实际上,即使对于大量的map类型,它仍然是相同的。在继续编写模板之前,有必要检查引用以回忆序列容器接受2个模板参数 - 对于值类型和分配器。 虽然分配器是默认的,但我们仍然应该在模板运算符<<中考虑它的存在:

template<typename T, template<class,class...> class C, class... Args>
std::ostream& operator <<(std::ostream& os, const C<T,Args...>& objs) {
    os << __PRETTY_FUNCTION__ << '\n';
    for (auto const& obj : objs)
        os << obj << ' ';
    return os;
}

int main() {
    std::vector<float> vf { 1.1, 2.2, 3.3, 4.4 };
    std::cout << vf << '\n';

    std::list<char> lc { 'a', 'b', 'c', 'd' };
    std::cout << lc << '\n';

    std::deque<int> di { 1, 2, 3, 4 };
    std::cout << di << '\n';

    return 0;
}

/// std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = float, C = vector, Args = <std::__1::allocator<float>>]
///1.1 2.2 3.3 4.4 
///std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = char, C = list, Args = <std::__1::allocator<char>>]
///a b c d 
///std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = int, C = deque, Args = <std::__1::allocator<int>>]
///1 2 3 4 

__PRETTY_FUNCTION__ 为非标准宏,

  1. 若用g++编译C++程序, __FUNCTION__ 只能输出类的成员函数名,不会输出类名。而 __PRETTY_FUNCTION__ 则会以 <return-type> <class-name>::<member-function-name>(<parameters-list>) 的格式输出成员函数的详悉信息(注: 只会输出 parameters-list 的形参类型, 而不会输出形参名).

  2. 若用gcc编译C程序,__PRETTY_FUNCTION____FUNCTION__ 的功能相同。

在C99标准中,加入了一个有用的、类似宏的表达式__func__,它会报告未修饰过的、正在被访问的函数名。__func__不是一个宏,因为预处理器对此函数一无所知。相反,它是作为一个隐式声明的常量字符数组实现的:static const char __func__[] = "function-name"; 。ISO C++却不完全支持所有的C99扩展,大多数的编译器提供商都使用__FUNCTION__取而代之,而__FUNCTION__通常是一个定义为 __func__的宏,之所以使用这个名字,是因为它已受到了大多数的广泛支持。
在Visual Studio 2005中,默认情况下,此特性是激活的。在IDE环境中,不能识别__func__,而要用__FUNCTION__代替。GCC 3.0及更高的版本同时支持__func____FUNCTION__
__FUNCDNAME__:返回函数的修饰名,__FUNCSIG__:返回返回签名。

例子:

int read(int x) {
    std::cout<<__FUNCTION__<<'\n'
            <<__FUNCDNAME__<<'\n'
            <<__FUNCSIG__<<std::endl;
}

int main() {
    read(5);
}
///read
///?read@@YAHH@Z
///int __cdecl read(int)
///int read(int)

std::tuple

1. make_tuple

template< class... Types >
tuple<VTypes...> make_tuple( Types&&... args );

创建一个tuple对象,并根据参数类型推导目标类型。返回值类型为:std::tuple<VTypes...>(std::forward<Types>(t)...)
其可能实现为:

template <class T>
struct unwrap_refwrapper {
    using type = T;
};

template <class T>
struct unwrap_refwrapper<std::reference_wrapper<T>> {
    using type = T&;
};

template <class T>
using special_decay_t = typename unwrap_refwrapper<typename std::decay<T>::type>::type;

template <class... Types>
auto make_tuple(Types&&... args) {
    return std::tuple<special_decay_t<Types>...>(std::forward<Types>(args)...);
}

2. tuple_cat

将参数中的所有tuple连接成一个tuple

例子:

template <typename Tuple, std::size_t N>
struct TuplePrint{
    static void print(const Tuple& t){
        TuplePrint<Tuple,N-1>::print(t);
        std::cout<<", "<<std::get<N-1>(t);
    }
};

template <typename Tuple>
struct TuplePrint<Tuple,1>{
    static void print(const Tuple& t){
        std::cout<<std::get<0>(t);
    }
};

template <typename...Args>
void print(const std::tuple<Args...>& t){
    std::cout<<"( ";
    TuplePrint<decltype(t),sizeof...(Args)>::print(t);
    std::cout<<" )\n";
}

int main(){
    std::tuple<int,std::string,float> t1(10,"test",3.14);
    int n=7;
    auto t2=std::tuple_cat(t1,std::make_pair("Foo","bar"),t1,std::tie(n));
    n=10;
    print(t2);
}
///    ( 10, test, 3.14, Foo, bar, 10, test, 3.14, 10 )

3. std::tie

Creates a tuple of lvalue references to its arguments or instances of std::ignore.

例子:

int main() {
    std::set<std::string> set_of_str;
    bool inserted;
    std::tie(std::ignore, inserted) = set_of_str.insert("Test");
    if (inserted) {
        std::cout << "Value was inserted sucessfully\n";
    }
}

上述代码,set::insert返回一个std::pair,其中first是插入元素的迭代器,第二个是bool,表示是否插入了元素。

std::tie创建一个左值引用tuple。 当利用insert进行插入时,它将std::tie中的变量设置为std::pairfirstsecond成员。

std::ignore

An object of unspecified type such that any value can be assigned to it with no effect. Intended for use with std::tie when unpacking a std::tuple, as a placeholder for the arguments that are not used.

这段代码的作用:忽略Test的的迭代器,并将assigned赋值给std::pair的成员second(用于表明一个元素是否被插入)。

tuple_element

在编译期根据索引,返回该tuple元素的type。
可能实现为:

template <std::size_t I, typename T>
struct tuple_element;

///    recursive case
template <std::size_t I, typename Head, typename...Tail>
struct tuple_element<I, std::tuple<Head, Tail...>> { };

///    base case
template <typename Head, typename...Tail>
struct tuple_element<0, std::tuple<Head, Tail...>> {
    typedef Head type;
};

例子:

template <typename...Args>
struct type_list {
    template <std::size_t I>
    using type = typename std::tuple_element<I, std::tuple<Args...>>::type;
};

int main() {
    std::cout<<std::boolalpha;
    type_list<bool,short,int>::type<0> x=1;
    std::cout<<x<<std::endl;
}
///    true

扩展:

  1. std::ignore的实现
  2. Implementing std::tuple

type_traits

使用traits的目的主要有三种:分派(dispatch)、效率以及使得某些代码通过编译。

分派

假设有一个模板函数,由一个动物园提供,接受所有的动物。于是它向外界提供一个函数接受register:

template <typename T>
void AcceptAnimals(T animal) {
    ... //impl
}

但如果想要将是狮子与熊猫分开处理(饲养两种动物并不相同,可能对狮子需要提供肉,而对于熊猫只需要提供竹子)。可能想到的方法是分别提供两个不同的函数:AcceptLionAcceptPanda,但是这种方法显得十分冗余(注册者可能既有狮子又有熊猫,这样就不得不调用不同的函数来注册,同时伴随着动物种类的增加,势必会导致向外提供的结构也随之增加,而注册者又不得不记住这些冗余的名字,而这显然没有只需要记住AcceptAnimal这一个名字来的简单)。如果想要保持这个模板函数,并将它作为向外界提供的唯一接口,则需要某种方式来获取类型T特征(trait),并按照不同的特征来采取不同的策略。

所以,解决办法是:规定所有的动物类(如class Lionclass Panda)都必须在类内部提供一个typedef类型,用于表明自己身份

struct lion_tag { };    //这只是一个empty class,目的是用于激发function overloading
struct panda_tag { };

对于所有的狮子类,则有:

class Lion {
public:
    typedef lion_tag type;    //类型标志,表明这是Lion类
    ...
}

此时,动物园便可以在内部分别提供对狮子和熊猫的处理函数:

template <typename T>
void Accept(T lion, lion_tag) { ... }

template <typename T>
void Accept(T panda, panda_tag) { ... }

最后,之前的Accept函数可以改写为:

tmeplate <typename T>
void Accept(T animal) {
//如果T是lion类,则typename T::type就是lion_tag,那么typename T::type()将创建一个lion_tag类的临时对象,根据函数重载的规则,将会调用Accept(T,lion_tag),这正是转向处理狮子的策略。
//typename用于告诉编译器T::type这是个类型,而不是static member
    Accept(animal, typename T::type());        //##
}

此时,所有的类型推导、函数重载都是在编译期完成,几乎不需要耗费任何runtime成本(除了创建lion_tag,panda_tag临时对象成成本,然而经过编译器的优化,这种成本将会消失)就能拥有可读性和可维护性高的代码。也许你会纳闷,traits在哪里,其实 typename T::type 就是 traits,只不过少了一层封装而已,可以如下改进:

template <typename T>
struct AnimalTraits {
    typedef T::type type;
}

所以,##处的代码可以改写为:

Accept(animal, typename AnimalTraits<T>::type());

使得某些代码能够通过编译

经过traits某些代码能够通过编译,例如std::pair的代码:

template <typename T1, typename T2>
struct pair {
    T1 first;
    T2 second;
    //如果T1或T2本身就是引用,则会出现编译错误,因为没有“引用的引用”
    pair(const T1& nfirst, const T2& nsecond):
        first(nfirst), second(nsecond) { }
};

此时,便可以使用一个traits(std::type_traits中的std::add_reference)来避免这样的编译错误。这个traits中包含一个typedef,如果add_reference<T>中的T为引用,则typedef T type;如果T不是引用,则有typedef T& type。于是上述代码可改写为:

pair(add_reference<const T1>::type nfirst, add_reference<cosnt T2>::type nsecond) ...

std::integral_constant

其可能实现为:

template <typename T, T v>
struct integral_constant {
    static constexpr T value;
    typedef T value_type;
    typedef integral_constant type;    //使用injected-class-name
    constexpr operator value_type() const noexcept { return value; }
    constexpr value_type operator () () const noexcept { return value; }

injected-class-name:在类作用域中,当前类的name被当做一个public member name。在name的声明处之后紧随的是类的定义**。即injected-class-name意味着X被当成X的一个成员,以至于在X内部进行name lookup是总是会找到当前类,而不是在同一个封闭作用域中声明的其他X

void X() { }
class X {
public:
  static X create() { return X(); }
};

create()函数是产生一个临时X对象还是条用函数X?在命名空间范围内(namespace scope),它会调用函数,因此injected-class-name的目的是为了确保在类X内,name总是找到class本身(因为name look总是会会子啊class自身的范围内开始,然后再查看enclosing scope)。

它在类模板内也十分有用,injected-class-name可以在没有模板参数列表的情况下使用。例如可以使用简单的Foo而不是完整的template-idFoo<bla,bla,bla>,因此可以很容易使用当前的实例(instantiation)。

例子:

int X;
struct X {
    void f() { 
        X* p;    //OK,X是一个injected-class-name
        ::X* q; //Error:name lookup找到的是变量名,它掩盖了struct 那么
    }
};

扩展阅读:Generic Programming Techniques

true_typefalse_type

typedef std::integral_constant<bool, true> true_type;
typedef std::integral_constant<bool, false> false_type;

std::is_same

可能实现为:

template <typename T, typename U>
struct is_same : std::false_type { }

template <typename T>
struct is_same<T, T> : std::true_type { }

std::add_pointer

可能实现为:

namespace detail {
template <typename T, bool is_function_type = false>
struct add_pointer {
    using type = typename std::reference<T>::type*;
}

template <typename T>
struct add_pointer<T, true> {
    using type = T;
};

template <typename T, typename...Args>
struct add_pointer<T(Args...), true> {
    using type = T(*)(Args...);
}

template <typename T, typename...Args>
struct add_pointer<T(Args..., ...), true> {
    using type = T(*)(Args..., ...);
};
}    //namespace detail

template <typename T>
struct add_pointer : detail::add_pointer<T, std::is_function<T>::value> { }

std::is_pointer

检查T是否为a pointer to object or a pointer to function(注意:不是成员指针或者成员函数指针)。可能实现为:

template <typename T>
struct is_pointer_helper : std::false_type { };

template <typename T>
struct is_pointer_helper<T*> : std::true_type { };

template <typename T>
struct is_pointer : is_ponter_helper<typename std::remove_cv<T>::type> { };

例子:

struct A {
    void func();
};

int main() {
    std::cout<< std::boolalpha;
    std::cout<< std::is_pointer<decltype(&A::func)>::value <<std::endl;
}
/// false

std::remove_cv

可能实现为:

template <typename T> struct remove_const { typedef T type; };
template <typename T> struct remove_const<const T> { typedef T type; };

template <typename T> struct remove_volatile { typedef T type; };
template <typename T> struct remove_volatile<volatile T>  { typedef T type; };

template <typename T>
struct remove_cv {
    typedef typename std::remove_volatile<typename std::remove_const<T>::type>::type type;
};

std::remove_reference

可能实现为:

template <typename T> struct remove_reference { typedef T type; };
template <typename T> struct remove_reference<T&> { typedef T type; };
template <typename T> struct remove_reference<T&&> { typedef T type; };

std::is_array

可能实现为:

template <typename T> struct is_array : std::false_type { };

template <typename T> struct is_array<T[]> : std::true_type { };

template <typename T, std::size_t N> struct is_array<T[N]> : std::true_type { };

std::rank

如果T是数组类型,将对外提供一个为数组维数的member constant value。若T为其他类型,则返回0
可能实现为:

template <typename T>
struct rank : public std::integral_constant<std::size_t, 0> { };

template <typename T>
struct rank<T[]> : public std::integral_cosntant<std::size_t, rank<T>::value + 1> { };

template <typename T, std::size_t>
struct rank<T[N]> : public std::integral_constant<std::size_t, rank<T>::value + 1> { };

std::extent

如果T是数组类型,则对外提供一个member constant value,它的值等于该数组第N维的元素个数(前提是N的范围为[0, std::rank<T>::value]。如果T是其他类型,或者是一个第一维大小未知其N0,则value的大小为0

可能实现为:

//此版本默认是数组第一维的大小(即以为数组的大小)
template <typename T, unsigned N = 0>
struct extent : std::integral_constant<std::size_t, 0> { };

template <typename T>
struct extent<T[], 0> : std::integral_constant<std::size_t> { };

template <typename T, unsigned N>
struct extent<T[], N> : std::extent<T, N-1> { };

template <typename T, std::size_t I>
struct extent<T[N], 0> : std::integral_constant<std::size_t, I> { };

template <typename T, std::size_t I, unsigned N>
struct extent<T[I], N> : std::extent<T, N-1> { };

例子:

int main(){
    std::cout<<std::extent<int[3]>::value<<std::endl;            //3
    std::cout<<std::extent<int[3][4], 1>::value<<std::endl;      //4
    std::cout<<std::extent<int[3][4], 2>::value<<std::endl;   //0
}

remove_extent

对于多维数组,则只移除第一维,可能实现是:

template <typename T>
struct remove_extent { typedef T type; }

template <typename T>
struct remove_extent<T[]> { typedef T type; }

template <typename T, std::size_t N>
struct remove_extent<T[N]> { typedef T type; }

例子:

template <typename T>
typename std::enable_if<std::rank<T>::value==1>::type
print(const T& t){
    std::copy(t,t+std::extent<T>::value,
        std::ostream_iterator<typename std::remove_extent<T>::type>(std::cout," "));
}

int main(){
    int a[][3]={{1,2,3},{4,5,6}};
    print(a[1]);    //4,5,6
}

is_convertible

原型为:

template <typename From, typename To>
struct is_convertible;

如果To test() { return std::declval<From>(); }为well-formed(合法的,即std::declval<From>能够用隐式转换转换为To,或 FromTo均为可有cv-qualified的void),则提供等于true的成员常量value。否则valuefalse

  1. boost is_convertible的实现
  2. is_convertible简易实现

std::conditional

其可能实现为:

template<bool B, class T, class F>
struct conditional { typedef T type; };

template<class T, class F>
struct conditional<false, T, F> { typedef F type; };

提供成员typedef type,若B在编译时为true则定义为T,或若Bfalse则定义为F

例子:

int main() {
    typedef std::conditional<true, int, double>::type Type1;
    typedef std::conditional<false, int, double>::type Type2;
    typedef std::conditional<sizeof(int) >= sizeof(double), int, double>::type Type3;

    std::cout << typeid(Type1).name() << '\n';
    std::cout << typeid(Type2).name() << '\n';
    std::cout << typeid(Type3).name() << '\n';
}
/// int 
/// double
/// double

如何理解以下这段来自utility file for VS 2017的代码(其中_Other1_Other2std::pair构造函数的参数,并利用Other1Other2来初始化firstsecond ):

 template<class _Other1,
    class _Other2,
    class = enable_if_t<is_constructible<_Ty1, _Other1>::value
                    && is_constructible<_Ty2, _Other2>::value>,
    enable_if_t<is_convertible<_Other1, _Ty1>::value
            && is_convertible<_Other2, _Ty2>::value, int> = 0>
    constexpr pair(pair<_Other1, _Other2>&& _Right)
        _NOEXCEPT_OP((is_nothrow_constructible<_Ty1, _Other1>::value
            && is_nothrow_constructible<_Ty2, _Other2>::value))
    : first(_STD forward<_Other1>(_Right.first)),
        second(_STD forward<_Other2>(_Right.second))
    {   // construct from moved compatible pair
    }

template<class _Other1,
    class _Other2,
    class = enable_if_t<is_constructible<_Ty1, _Other1>::value
                    && is_constructible<_Ty2, _Other2>::value>,
    enable_if_t<!is_convertible<_Other1, _Ty1>::value
            || !is_convertible<_Other2, _Ty2>::value, int> = 0>
    constexpr explicit pair(pair<_Other1, _Other2>&& _Right)
        _NOEXCEPT_OP((is_nothrow_constructible<_Ty1, _Other1>::value
            && is_nothrow_constructible<_Ty2, _Other2>::value))
    : first(_STD forward<_Other1>(_Right.first)),
        second(_STD forward<_Other2>(_Right.second))
    {   // construct from moved compatible pair
    }

I think is_constructible is enough, why are we using is_convertible here?

在这里使用is_convertible的目的是为了处理显示构造(explicit construction)。现在只考虑is_constructible,并试图编写一个wrapper(在这里使用REQUIRES来隐藏想要的任何SFINAE方法):

template <class T>
class wrapper {
public:
    template <class U, REQUIRES(std::is_constructible<T, U&&>::value)>
    wrapper(U&& u) : val(std::forward<U>(u)) { }
private:
    T val;
};

利用上述代码,可以得到:

struct Imp { Imp(int ); };
struct Exp { explicit Exp(int ); };

Imp i = 0; // ok
Exp e = 0; // error
wrapper<Imp> wi = 0; // ok
wrapper<Exp> we = 0; // ok?!?

我们绝对不希望最后一个能够通过编译,因为这样会破坏了Exp的期望。现在,如果可以从U &&直接初始化T,则is_constructible <T,U &&>true—如果T(std :: declval <U &&>())是valid expression。

另一方面,is_convertible <U &&,T>检查是否可以从U &&拷贝初始化(copy-initialize)T。 也就是说,如果T copy() { return std :: declval <U &&>(); }是valid。

不同之处在于,如果转换是explicit,则后者不起作用:

type is_constructible<T, int> is_convertible<int, T>
Impl true_type true_type
Exp true_type false_type

为了正确传播explicitness,我们需要同时使用两个traits—可以从中创建meta-trait:

template <class T, class From>
using is_explicitly_constructible = std::integral_constant<bool,
    std::is_constructible<T, From>::value &&
    !std::is_convertible<From, T>::value>;

template <class T, class From>
using is_implicitly_constructible = std::integral_constant<bool,
    std::is_constructible<T, From>::value &&
    std::is_convertible<From, T>::value>;

由于这两个traits是不重叠的,所以可以编写两个不同的ctor模板,其中一个ctor是显式的,而另一个不是:

template <class T>
class wrapper {
public:
    template <class U, REQUIRES(is_explicitly_constructible<T, U&&>::value)>
    explicit wrapper(U&& u) : val(std::forward<U>(u)) { }

    template <class U, REQUIRES(is_implicitly_constructible<T, U&&>::value)>
    wrapper(U&& u) : val(std::forward<U>(u)) { }
private:
    T val;
};

接下来便可以如我们所愿:

wrapper<Imp> wi = 0; // okay, calls non-explicit ctor
wrapper<Exp> we = 0; // error
wrapper<Exp> we2(0); // ok

扩展:std::conditional vs std::enable_if

std::decay

将类型T进行lvalue-to-rvalue,array-to-pointer以及function-to-pointer的隐式转换,移除cv-qualifiers,并对外提供一个typedef type成员:

  • T为“array of U”或“reference to array of U”类型,则成员typedef typeU*
  • 否则,若T为function type F或reference to function type,则成员 typedef typestd::add_pointer<F>::type
  • 否则,成员typedef typestd::remove_cv<std::remove_reference<T>::type>::type

其可能实现为:

template< class T >
struct decay {
private:
    typedef typename std::remove_reference<T>::type U;
public:
    typedef typename std::conditional< 
        std::is_array<U>::value,
        typename std::remove_extent<U>::type*,
        typename std::conditional< 
            std::is_function<U>::value,
            typename std::add_pointer<U>::type,
            typename std::remove_cv<U>::type
        >::type
    >::type type;
};

例子:

template <typename T, typename U>
struct decay_equiv : 
    std::is_same<typename std::decay<T>::type, U>::type { };

int main() {
    std::cout << std::boolalpha
              << decay_equiv<int, int>::value << '\n'
              << decay_equiv<int&, int>::value << '\n'
              << decay_equiv<int&&, int>::value << '\n'
              << decay_equiv<const int&, int>::value << '\n'
              << decay_equiv<int[2], int*>::value << '\n'
              << decay_equiv<int(int), int(*)(int)>::value << '\n';
}

///true
///true
///true
///true
///true
///true

实现tuple

现在实现一个tuple可以使用C++ 11的新特性:variadic template,因为创建一个tuple需要任意数量的类型。首先需要一个class declaration:

template <typename...Types>
struct tuple;

接下来需要考虑的问题是:怎么表示tuple中的元素,即tuple应该具备怎样的成员。一个可靠的想法是让tuple直接继承它自己的types:

template <typename...Types>
struct tuple : Types... { };

但这是一个坏主意,因为:

  • 不能从像intchar之类的类型进行继承
  • 不能同时继承同一个类型多次,即利用这种方法,tuple<string, string>是不可能实现的

所以,可以从n个类型中获取参数T1,...,Tn,而不是从T1,...,Tn直接继承得到。此时,需要一个wrapper类型,称之为tuple_elementtuple将从tuple_element派生n次。现在只需要考虑如何声明tuple_element

template <typename T>
struct tuple_element {
  T value_;
};

但应该怎样使得tuple可以从tuple_element派生n次:

template <typename... Types>
class tuple : tuple_element<Types>...{ };

虽然这比直接从T1, …, Tn继承要好,但仍然有问题。因为如果一些types相同,则它依旧无法正常运行。例如实例化一个tuple<int, int>,但不能从tuple_element<int>派生两次。可以使用添加索引的方式来消除tuple_element基类的歧义。

template <size_t I, typename T>
struct tuple_element {
  T value_;
};

这样,tuple_element<0, int>tuple_element<1, int>便不再是相同类型,所以可以同时从这二者派生。现在问题是如何做到这一点:tuple如何从每个tuple_element派生出类型T1,...,Tn以及合适的索引0,...,n-1。现在需要在下面的类中加入什么:

template <typename... Types>
class tuple : tuple_element<???, Types>... { };

现在需要将???占位符更换为参数包size_ts。 如果已经有用户提供的这种参数包便可以执行以下操作:

template <size_t... Indices, typename... Types>
class tuple : tuple_element<Indices, Types>... {
};

如果IndicesTypes是不同长度的参数包怎么办?这将造成编译错误。但如果两个参数包具有相同的长度,则包扩展运算符(pack expansion operator)(...)将使用index和type成功扩展每个tuple_element基类。 可以如下使用此类:

tuple<0, 1, int, string> tup;

现在需要自己提供索引(indices)。首先,将tuple类与indices分离—不希望用户看到额外所需的模板参数:

template <size_t... Indices, typename... Types>
struct tuple_impl : tuple_element<Indices, Types>...
{
};

template <typename... Types>
class tuple : tuple_impl<???, Types...>
{
};

tuple类应该怎样为所有indices提供tuple_impl? 首先要引入一些辅助类(supporting types)。 先从最简单的开始:

template <size_t... Indices>
struct index_sequence {
  using type = index_sequence<Indices...>;
};

index_sequence只不过是一个封装了size_t参数包的类型(注:index_sequence和其他supporting types已经成为C++ 14标准)。
现在应该解决如何使用它,如何从sequence中提取出indices的问题。即要如何编写一个拥有index_sequence并打印序列(sequence)中所有整数的函数?以下是一个解决方案:

void f(size_t s) { cout << s << '\n'; }

template <size_t... Indices>
void g(index_sequence<Indices...>) {
  int ignore[] { 
      (f(Indices), 0)... 
  };
  (void) ignore; // silence compiler warnings about the unused local variable
}

g(index_sequence<1, 2, 3, 4, 5>{});

在上述代码中,函数f只print一个size_t元素。 接下来,函数g需要一系列索引(注意:该参数没有名称—实际上并不关心它的名称。 它只是一种将数据导入函数的方法,而有用的数据在参数包中)。 在g里面我们想为每个索引调用f。然而不幸的是,它不支持如下所示的的naked expansion:

f(Indices)...;

这就是为什么不得不使用奇怪的解决方法:用扩展的逗号表达式(f(Indices),0)声明了一个local array。 当用Indices = [1,2,3]调用g时,该表达式扩展为(f(1),0),(f(2),0),(f(3),0)。 以逗号分隔的0列表进入local array。最后,在调用g时,用户只需提供index_sequence的实例即可。 编译器从实例中推断出Indices参数包。 但是应该如何使用这种技术,以便能够从tuple_element派生出每个index?

现在需要一个拥有三种类型(T0T1T2)的tuple。可以使用如下的代码:

template <typename T0, typename T1, typename T2>
class tuple : tuple_impl<0, 1, 2, T0, T1, T2> {

};

对于三种类型来说非常好,但是当给定类型参数包(typename ... Types)时,应该如何使索引出现在列表中? 可以尝试将index_sequence集成进来,重写tuple_impl

template <typename Sequence, typename... Types>
struct tuple_impl; // undefined base; parameters are named for clarity only

template <size_t... Indices, typename... Types>
struct tuple_impl<index_sequence<Indices...>, Types...>
  : tuple_element<Indices, Types>... {
};

第二个tuple_impl是模板特化,其参数共两个:参数包...Indices和类型参数…Types,此特化版本匹配Sequence = index_sequence的情况。

现在需要一个拥有三种类型(T0T1T2)的tuple,可以做以下事情:

template <typename T0, typename T1, typename T2>
class tuple : tuple_impl<index_sequence<0, 1, 2>, T0, T1, T2> {

};

现在,当用n个types实例化tuple时,只需要使用index_sequence <0,...,n-1>,而不必使用任意索引序列(arbitrary sequence of indices),称之为make_index_sequence(它是一个meta-function,也就是说,make_index_sequence::type将成为index_sequence <0,...,n-1>的别名(alias),using type = index_sequence <0,...,n-1 )。

实现make_index_sequence

template <size_t I, typename Sequence>
struct cat_index_sequence;

template <size_t I, size_t... Indices>
struct cat_index_sequence<I, index_sequence<Indices...>>
  : index_sequence<Indices..., I>
{
};

template <size_t N>
struct make_index_sequence
  : cat_index_sequence<N-1, typename make_index_sequence<N-1>::type>::type
{
};

template <>
struct make_index_sequence<1> : index_sequence<0> {};

cat_index_sequence是一个meta-function,它将index添加到当前索引序列的末尾,然后递归地将元素添加到增长的索引序列的末尾,以此来定义make_index_sequence元。 递归条件为make_index_sequence <1>,即index_sequence <0>

此时重复tuple_elementtuple_impl的定义,只是为了让整个事情更加清晰:

template <size_t, typename T>
struct tuple_element
{
  T value_;
};

template <typename Sequences, typename... Types>
struct tuple_impl; // undefined base; parameters are named for clarity only

template <size_t... Indices, typename... Types>
struct tuple_impl<index_sequence<Indices...>, Types...>
  : tuple_element<Indices, Types>...
{
};

template <typename... Types>
class tuple
  : tuple_impl<typename make_index_sequence<sizeof...(Types)>::type, Types...>
{
};

sizeof...(Types)tuple中元素的数量(n),make_index_sequence生成index_sequence<0,1,...,n-1>,用索引实例化tuple_impl,以便它可以为每个索引从tuple_element进行派生。

此时,当使用tuple<int,string,float>时,将得到一个派生自tuple_impl<index_sequence<0,1,2>,int,string,float>的类型,后者又从tuple_element<0,int>tuple_element <1,string>tuple_element<2,float>派生。

接下来需要解决tuple对象的构造问题,即需要向tuple_element添加一些操作使其变得更有用。 例如可以根据它的值类型(value type)来构造它:

explicit tuple_element(T const& value) : value_(value) {}
explicit tuple_element(T&& value) : value_(std::move(value)) {}

现在为tuple类创建一些基本操作(fundamental operations),以便可以更加简易地创建tuple。 需要定义下列操作:

  1. 默认构造函数(default ctor)
  2. 拷贝构造函数(copy ctor)
  3. 移动构造函数(move ctor)
  4. 拷贝赋值运算符(copy assignment operator)
  5. 移动赋值运算符(move assignment operator)
tuple() = default;
tuple(tuple const&) = default;
tuple(tuple&&) = default;
tuple& operator=(tuple const& rhs) = default;
tuple& operator=(tuple&&) = default;

对于n个类型的tuple,它需要n个元素并初始化该tuple,即按照如下的方式使用它:

tuple<int, float> t1(3, 3.14f);                 // exact match
tuple<long long, double> t2(3, 3.14f);  // widening conversions
tuple<string> t3("Hello, World");            // string construction from char const[]

显然,可以将这个构造函数写成一个template。 此外,它将采用万能引用(universal reference)—希望它支持lvalue和rvalue并实现完美转发(perfect forwarding)。

using base_t = tuple_impl<typename make_index_sequence<sizeof...(Types)>::type, Types...>;

template <typename... OtherTypes>
explicit tuple(OtherTypes&&... elements)
  : base_t(std::forward<OtherTypes>(elements)...)
{
}

tuple对其元素的存储方式一无所知。 因此,它只是将参数转发(forward)给tuple_impl,而tuple_impl又需要将参数转发到其tuple_element基类。

template <typename... OtherTypes>
explicit tuple_impl(OtherTypes&&... elements)
  : tuple_element<Indices, Types>(std::forward<OtherTypes>(elements))...
{
}

现在希望用base ctor调用参数包中的每个元素展开形式:tuple_impl<N, T>(std::forward<U>(e))。 这要求...Indices, ...Types...OtherTypes参数包都具有相同的长度(否则会出现编译错误)。此外还需要在一些重载决议(overload management)中进行throw,以便在参数数量出错时不考虑这些构造函数:

template <typename... OtherTypes,
          typename = typename std::enable_if<
            sizeof...(OtherTypes) == sizeof...(Types)>::type>
explicit tuple(OtherTypes&&... elements)
  : base_t(std::forward<OtherTypes>(elements)...)
{
}
// and the same thing applies to tuple_impl's constructor

但上述代码有可能造成极其危险的后果。 对于单元素(single-element)tuple,此构造函数极有可能会影响拷贝构造函数。 考虑以下示例:

tuple<int> t1(3);  // good, construct from rvalue int
tuple<int> t2(t1); // which constructor are we calling?

也许令人惊讶的是,第二行将使用OtherTypes = tuple<int>&调用模板构造函数(template ctor),它比拷贝构造函数(它采用tuple <int> const&)以及移动构造函数(它采用tuple<int>&&)能够更好地进行匹配。所以我们试图用OtherTypes = tuple<int>&来调用tuple_impl的构造函数,它反过来试图用tuple <int>&来调用tuple_element <0, int>的构造函数,它会毫不意外地失败。解决办法是可以为tuple_element添加一个通用构造函数(universal ctor):

template <typename U>
explicit tuple_element(U&& value) : value_(std::forward<U>(value)) {}

但它其实没什么用—如果仍然用tuple <int>&来初始化tuple_element<0,int>,则意味着tuple<int>&将被用来初始化int(tuple_element<0, int>::value_)

为了避免这种状况发生,需要再添加一些重载决议的逻辑。 简而言之,当处理的元素不是tuple时,则希望tuple的通用构造函数(universal ctor)能够reject tuples。 更重要的是,希望tuple<short>可以通过拷贝构造得到tuple <int>(这两者是不同的类型):

template <typename... OtherTypes>
explicit tuple(tuple<OtherTypes...> const& rhs) : base_t(rhs) {}

template <typename... OtherTypes>
explicit tuple(tuple<OtherTypes...>&& rhs) : base_t(std::move(rhs)) {}

This seems to further complicate things, because tuple_impl‘s constructor can now be called with its “home” tuple, tuples of some other types, and naked lists of the tuple’s elements.Here’s how we can fix this. The key problem lies within tuple_impl. It has to be able to tell other tuple_impl‘s (which represent copy/move construction) from anything else, which should be used to initialize the tuple_element‘s directly.

现在实现一个返回bool值的meta-function is_tuple_impl <T>,它用于判断T是否为tuple_impl

template <typename>
struct is_tuple_impl
  : std::false_type {};

template <size_t... Indices, typename... Types>
struct is_tuple_impl<tuple_impl<index_sequence<Indices...>, Types...>>
  : std::true_type {};

有了这个meta-function,就可以去除tuple_impl的通用构造函数了。本质上,如果类型参数包中的元素的类型符合is_tuple_impl,则希望从重载集中删除构造函数(overload set)。

实现返回bool值的meta-function is_any_of <Op, ...Types>,它确定类型参数包…Types中的任意类型T是否满足Op <T>::value == true(注意,Op必须是template template parameter)。

现在使用constexpr函数来改进它,而不是使用无聊的false_type/true_type类:

template <template <class> typename>
constexpr bool is_any_of()
{
  return false;
}

template <template <class> typename Op, typename Head, typename... Tail>
constexpr bool is_any_of()
{
  return Op<Head>::value || is_any_of<Op, Tail...>();
}

对于使用is_any_of<is_tuple_impl, ...>()的类型列表而言,将获得一个编译时(compile-time)布尔值,用于表示这些类型中是否存在tuple_impl。 现在进行重载决议:

template <typename... OtherTypes,
          typename = typename std::enable_if<
            !is_any_of<is_tuple_impl, typename std::decay<OtherTypes>::type...>()
          >::type
         >
explicit tuple_impl(OtherTypes&&... elements)
  : tuple_impl<Indices, Types>(std::forward<OtherTypes>(elements))...
{
}

此处需要使用std::decay的原因:例如,如果使用lvalue tuple_impl进行调用,则OtherTypes可能包含tuple_impl&。除非将&删除,否则将被is_tuple_impl reject。

虽然拥有了一个可构造的tuple(constructible tuple)之后,但是没有办法通过索引或任何其他方式获取tuple中的元素。现在需要实现主访问器(main accessor):get<>,用来读取和编写tuple中的元素。 get<>模板的实现有两种方式:

  • get by index
  • get by type

后者是自C ++ 14以来tuple interface的一部分。可以根据前者来实现后者。以下是get<>所需的重载:

template <size_t I, typename... Types>
??? get(tuple<Types...> const& tup);

template <size_t I, typename... Types>
??? get(tuple<Types...>& tup);

template <size_t I, typename... Types>
??? get(tuple<Types...>&& tup);

很明显,上述三个函数的返回类型和内容都非常相似,只需要确定它们的返回类型应该是什么。

实现meta-function type_at_index<I, Types...>,它的返回类型参数包...Types中第1个元素的类型。利用可变参数模板递归实现:

template <size_t I, typename Head, typename... Tail>
struct type_at_index
{
  using type = typename type_at_index<I-1, Tail...>::type;
};

template <typename Head, typename... Tail>
struct type_at_index<0, Head, Tail...>
{
  using type = Head;
};

template <size_t I, typename... Types>
using type_at_index_t = typename type_at_index<I, Types...>::type;
Great. We can now fill in the return types:

template <size_t I, typename... Types>
type_at_index_t<I, Types...> const& get(tuple<Types...> const& tup);

template <size_t I, typename... Types>
type_at_index_t<I, Types...>& get(tuple<Types...>& tup);

template <size_t I, typename... Types>
type_at_index_t<I, Types...>&& get(tuple<Types...>&& tup);

现在填写返回类型:

template <size_t I, typename... Types>
type_at_index_t<I, Types...> const& get(tuple<Types...> const& tup);

template <size_t I, typename... Types>
type_at_index_t<I, Types...>& get(tuple<Types...>& tup);

template <size_t I, typename... Types>
type_at_index_t<I, Types...>&& get(tuple<Types...>&& tup);

但是第三个声明是错误的。 如果type_at_index返回一个左值引用类型(lvalue reference type),那么返回类型也将是一个左值引用,因为引用折叠(reference collapsing)会将&&&变为。 例如:

int x = 42;
tuple<int&> t(x);
get<0>(std::move(t));    // returns int&

这很容易解决:只需要删除引用修饰符(reference qualifier)&

template <size_t I, typename... Types>
std::remove_reference_t<type_at_index_t<I, Types...>>&& get(tuple<Types...>&& tup);

好的,现在这些方法的主体怎么样? 从一开始就设计了tuple,以简化实现,然后tuple_element也已经已经注入索引,所以只需要将tuple实例强制转换为适当的基类,然后提取value字段即可。 例如:

template <size_t I, typename... Types>
type_at_index<I, Types...>& get(tuple<Types...>& tup)
{
  tuple_element<I, type_at_index_t<I, Types...>>& base = tup;
  return base.value_;
}

另外两种方法显然相似。 现在让我们来看看另一种getget<T>。 它需要验证类型在tuple中只出现一次,并返回该的元素。例如:

tuple<int, string> t1;
get<int>(t1) = 42;
get<float>(t1) = 3.14f;    // compilation error

tuple<int, int> t2;
get<int>(t2) = 42;    // compilation error

基本方法如下:计算T出现在tuple中的次数。 如果次数不是1,则throw编译错误。 否则,找到类型为T时的索引I并调用get<I>。 此时将再次使用constexpr函数进行大部分工作,因为它比使用struct更容易,更优雅:

template <typename>
constexpr int count()
{
  return 0;
}
template <typename T, typename Head, typename... Tail>
constexpr int count()
{
  return (std::is_same<T, Head>::value ? 1 : 0) + count<T, Tail...>();
}

template <typename>
constexpr int find(int)
{
  return -1;
}
template <typename T, typename Head, typename... Tail>
constexpr int find(int current_index = 0)
{
  return std::is_same<T, Head>::value
    ? current_index
    : find<T, Tail...>(current_index + 1);
}

template <typename T, typename... Types>
T& get(tuple<Types...>& tup)
{
  static_assert(count<T, Types...>() == 1, "T must appear exactly once in ...Types");
  get<find<T, Types...>()>(tup);
}

此外,还可以在tuple类上实现一些额外的功能,例如:

  • Copy and move assignment operators from tuple
  • Comparison operators: ==, !=, <, <=, >, >=
  • Tuple construction from std::pair and std::array

接下来实现一些tuple非成员函数和helpers。首先,实现一个非常简单的helper:tuple_size, 它是一个返回tuple中元素数量的元函数。

template <typename>
struct tuple_size; // undefined base template

template <typename... Types>
struct tuple_size<tuple<Types...>> : std::integral_constant<size_t, sizeof...(Types)> {

};

接着实现forward_as_tuple。Basic idea:提供一组元素,并且根据元素的值类别(value categories,即lavalue、rvalue等)返回具有左值或右值引用的tuple。 例如:

string s, t;
auto t1 = forward_as_tuple(s, t);       // t1 is tuple<string&, string&>
auto t2 = forward_as_tuple(move(s), t); // t2 is tuple<string&&, string&>

forward_as_tuple的实现:

template <typename... Types> 
tuple<Types&&...> forward_as_tuple(Types&&... elements)
{
  return tuple<Types&&...>(std::forward<Types>(elements)...);
}

接下来实现tie, 它是一个非常有用的helper,可以将tuple绑定到单个变量上。 例如(第二个例子假设tuple有一个接受std::pair的赋值运算符):

int status_code; 
string status;
tie(status_code, status) = make_http_request(url); // returns tuple<int, string>

set<string> names;
bool inserted; 
set<string>::iterator iter;
tie(inserted, iter)   = names.insert("Sasha");
tie(inserted, ignore) = names.insert("Sasha"); // assigns only to 'inserted'

实现tie函数虽然看起来很难,其实只是创建一个带左值引用的tuple的问题! 然后,这些引用可以由赋值运算符进行赋值。

template  <typename... Types>
tuple<Types&...> tie(Types&... elements)
{
  return tuple<Types&...>(elements...);
}

“ignore”用于简化类型实例,它可以忽略任何赋值尝试:

struct ignore_t {
  template <typename U>
  ignore_t& operator=(U&&) {
    return *this;
  }
} ignore;

最后,需要实现make_tuple,它将一组元素打包成一个tuple(需要注意make_tuple如何对待std::reference_wrapper)。

实现tuple_cat,它将用于连接任意数量的元组。 例子:

auto t1 = tuple_cat(make_tuple(1), make_tuple(2)); // tuple<int, int>(1, 2)
auto t2 = tuple_cat(t1, make_tuple(3), make_tuple(4));
auto t3 = tuple_cat(t1, t1, t2, t2);
// t3 is a tuple of 12 ints: 1, 2, 1, 2, 1, 2, 3, 4, 1, 2, 3, 4

显然,tuple_cat是一个variadic function template。 tuple本身是一个variadic class template,现在需要尝试构建一个variadic function template,用于获取任意数量的variadic class template。先尝试解决一个简单的问题:给定一个元组,应该怎样调用一个接受所有tuple元素的函数?

编写一个函数explode,它接受任意长度的tuple并将其所有元素传递给另一个函数f。基本的想法是使用index_sequence。对于大小为ntuple,需要生成index_sequence<0,1,...,n-1>。 然后,index_sequence的索引可以与get<I>函数一起使用,提取tuple中的元素。 由于不需要访问tuple的类型,因此可以简单地使用万能引用(universal reference),它可以简化代码并正确转发rvalue tuple

template <typename Tuple, size_t... Indices>
void explode1(Tuple&& tup, index_sequence<Indices...>) {
  f(get<Indices>(std::forward<Tuple>(tup))...);
}

template <typename Tuple>
void explode(Tuple&& tup) {
  explode1(
    std::forward<Tuple>(tup),
    make_index_sequence<tuple_size<std::decay_t<Tuple>>::value>{}
  );
}

然而,index_sequence再次罢工!关键是调用get<Indices>(tup)...,它将扩展为getget<0>(tup), get<1>(tup), ..., get<n-1>(tup)的多个调用,其中ntuple的大小。 现在,如果有两个tuple,并且f被称为make_tuple 怎么办?

编写一个简化版本的tuple_cat,它连接两个tuple

template <typename Tuple1, size_t... Indices1, typename Tuple2, size_t... Indices2>
auto tuple_cat1(Tuple1&& tup1, Tuple2&& tup2,
                index_sequence<Indices1...>, index_sequence<Indices2...>) {
  return make_tuple(
    get<Indices1>(std::forward<Tuple1>(tup1))...,
    get<Indices2>(std::forward<Tuple2>(tup2))...
  );
}

template <typename Tuple1, typename Tuple2>
auto tuple_cat(Tuple1&& tup1, Tuple2&& tup2) {
  tuple_cat1(
   std::forward<Tuple1>(tup1),
   std::forward<Tuple2>(tup2),
   make_index_sequence<tuple_size<std::decay_t<Tuple1>>::value>{},
   make_index_sequence<tuple_size<std::decay_t<Tuple2>>::value>{}
  );
}

因此,基本思想是调用make_tuple,并将两个tuple的元素作为其参数进行展开。 这两个参数包…Indices1…Indices2有助于扩展每个单独的tuple

template <size_t, typename>
struct type_at_tuple; // undefined base template

template <size_t I, typename... Types>
struct type_at_tuple<I, tuple<Types...>> : type_at_index<I, Types...>
{
};

template <typename, typename, typename, typename>
struct cat1_t; // undefined base template

template <typename Tuple1, size_t... Indices1, typename Tuple2, size_t... Indices2>
struct cat1_t<
  Tuple1, index_sequence<Indices1...>,
  Tuple2, index_sequence<Indices2...>
> {
  using type = tuple<
    typename type_at_tuple<Indices1, Tuple1>::type...,
    typename type_at_tuple<Indices2, Tuple2>::type...
  >;
};

template <typename Tuple1, typename Tuple2>
struct cat_t {
  static constexpr size_t Size1 = tuple_size<std::decay_t<Tuple1>>::value;
  static constexpr size_t Size2 = tuple_size<std::decay_t<Tuple2>>::value;
  using Seq1 = typename make_index_sequence<Size1>::type;
  using Seq2 = typename make_index_sequence<Size2>::type;
  using type = typename cat1_t<Tuple1, Seq1, Tuple2, Seq2>::type;
};

现在可以用typename cat_t<Tuple1, Tuple2>::type作为tuple_cat的返回类型(如果编译器不支持C++ 14,则可以用这种方式)。

实现通用版本的tuple_cat,用于连接任意数量的tuple(假设前一个示例中的tuple_cat仍然存在,并连接两个tuple。现在将其重命名为tuple_cat2,并递归地使用它:):

template <typename HeadTuple>
HeadTuple&& tuple_cat(HeadTuple&& tup) {
  return std::forward<HeadTuple>(tup);
}

template <typename Head1Tuple, typename Head2Tuple, typename... TailTuples>
auto tuple_cat(Head1Tuple&& tup1, Head2Tuple&& tup2, TailTuples&&... tail) {
  return tuple_cat(
    tuple_cat2(
      std::forward<Head1Tuple>(tup1),
      std::forward<Head2Tuple>(tup2)
    ),
    std::forward<TailTuples>(tail)...
  );
}

如果当前编译器不支持C++ 14,则可以用cat_t模板来支持任意数量的tuple

template <typename, typename>
struct cat_two_t; // undefined base template

template <typename... Types1, typename... Types2>
struct cat_two_t<tuple<Types1...>, tuple<Types2...>> {
  using type = tuple<Types1..., Types2...>;
};

template <typename...>
struct cat_many_t; // undefined base template

template <typename Tuple>
struct cat_many_t<Tuple> {
  using type = Tuple;
};

template <typename Tuple1, typename Tuple2, typename... Tuples>
struct cat_many_t<Tuple1, Tuple2, Tuples...> {
  using two_t = typename cat_two_t<Tuple1, Tuple2>::type;
  using type = typename cat_many_t<two_t, Tuples...>::type;
};

扩展:Implementing std::tuple From The Ground Up: Part 7, tuple_cat Take 2

CRTP

curiously recurring template pattern:把派生类作为基类的模板参数。其实质就是一种静态多态。

特点:

  • 继承自template class
  • 使用派生类作为模板参数来特化基类

CRTP模板:

template <typename T>
class Base {
    ...
};
// use the derived class itself as a template parameter of the base class

class Derived : public Base<Derived> {
    ...
};

上述代码的目的是:在基类中使用派生类,从基类的角度看,派生类其实也是基类(通过向下转型做到)。故可以通过static_cast把基类转换到派生类,从而使用派生类的成员
形式如下:

template <typename T>
class Base {
public:
    void doWhat() {
        T& derived = static_cast<T&>(*this);
        // use derived...
    }
};

注意: 这里不使用dynamic_cast是因为dynamic_cast一般是为了确保在运行期(run-time)时进行转换的正确性。CRTP的设计的目的是为了将派生类作为基类的模板参数(the Base class is designed to be inherited from by its template parameter),此外再无他求。因此使用static_cast便足矣。

1. 易错点

当两个类继承自同一个CRTP 基类时,如下代码所示时将会出现错误(Derived2派生的基类模板参数应该为Derived2):

class Derived1 : public Base<Derived1> {
    ...
};
class Derived2 : public Base<Derived1>  {// bug in this line of code
    ...
};

为了防止种错误的出现,可以写成如下的代码形式:

template <typename T>
class Base {
public:
    // ...
private:// import 
    Base(){};
    friend T;
};

解决办法是:在基类中添加一个private ctor,并且将模板参数T声明为Base类的友元。这样做可行是因为,派生类会调用基类的ctor(即使没有在代码中明确说明,编译器也这样做)。由于Base的ctor为private,除友元之外没有,其他东西可以访问,而基类唯一的friend class,就是template class。因此,如果派生类与模板类(template class)不同,则代码编译不过

派生类会隐藏和基类同名的方法(因为被继承类的实现方法遮掩了,即基类的do方法被隐藏了),如下代码所示:

template <typename T>
class Base {
public:
    void do();
};
class Derived : public Base<Derived> {
public:
    void do(); // oops this hides the doSomething methods from the base class !
}

CRTP两种常见用法:

  • Adding functionality(通过继承基类(CRTP)来扩展派生类的接口)
  • Static Interfaces(利用静态多态,编译期多态)

add functionality

有些类提供了generic functionality,可以被许多其他类reuse。为了说明这一点,以一个表示的sensitivity类为例:

class Sensitivity {
public:
    double getValue() const;
    void setValue(double value);

    void scale(double multiplicator) {
        setValue(getValue() * multiplicator);
    }

    void square() {
        setValue(getValue() * getValue());
    }

    void setToOpposite() {
        scale(-1);
    };
};

如果现在有另一个类,它也有一个value,并且还拥有同Sensitivity一样的function, 难道要将这几个function又原封不动地copy-and-paste到新类吗?这就太违背OOP的设计初衷了,此时可以将这几个function提取出来封装成一个NumericalFunctions类:

template <typename T>
struct NumericalFunctions {
void scale(double multiplicator);
void square();
void setToOpposite();
};

然后再使用CRTP将Sensitivity重写:

class Sensitivity : public NumericalFunctions<Sensitivity> {
public:
    double getValue() const;
    void setValue(double value);
    // rest of the sensitivity's rich interface...
};

为此,这几个method需要访问Sensitivity类的getValuesetValue方法进行实现:

template <typename T>
struct NumericalFunctions {
    void scale(double multiplicator) {
        T& underlying = static_cast<T&>(*this);
        underlying.setValue(underlying.getValue() * multiplicator);
    }

    void square() {
        T& underlying = static_cast<T&>(*this);
        underlying.setValue(underlying.getValue() * underlying.getValue());
    }

    void setToOpposite(){
        scale(-1);
    };
};

通过使用CRTP,就能有效地将功能添加到Sensitivity类中,这就是add functionality

为什么不使用template non-member functions而选择CRTP。它们可能如下所示:

template <typename T>
void scale(T& object, double multiplicator) {
    object.setValue(object.getValue() * multiplicator);
}

template <typename T>
void square(T& object) {
    object.setValue(object.getValue() * object.getValue());
}

template <typename T>
void setToOpposite(T& object) {
    object.scale(object, -1);

在non-member template function中使用CRTP至少需要一个参数:CRTP在如下接口(interface)<>中显示的那样。使用CRTP,可以看到Sensitivity提供了NumericalFunctions接口:

class Sensitivity : public NumericalFunctions<Sensitivity> {
public:
    double getValue() const;
    void setValue(double value);
    // rest of the sensitivity's rich interface...
};

即使知道这三个non-member functions的存在,也不能保证它们与特定的class兼容(可能它们调用get()getData(),而不是getValue())。 而对于用CRTP进行代码绑定(code binding)的Sensitivity早就已经编译完成,因此可以知道它们具备兼容的接口(compatible interface)。

CRTP中的inheritance与classical inheritance的区别:

【注意】:尽管CRTP使用了继承(inheritance),但使用它的过程同其他继承却不尽相同。通常,从另一个类派生的类表示derived class somehow conceptually “is a” base class。它的目的是能够在通用代码(generic code)中使用基类,同时使得对基类的调用能够redirect到派生类。

CRTP的情况完全不同:The derived class does not express the fact it “is a” base class。相反,它通过继承基类来扩展其接口,以便添加更多功能。

因此,基类不是interface,派生类也不是implementation。相反,它的meaning是:基类使用派生类方法(the base class uses the derived class methods)(例如getValuesetValue)。 在这方面,派生类为基类提供了接口。

Static interfaces

在这种情况下,the base class represent the interface, the derived one represent the implementation,同traditional polymorphism是一致的,只是与traditional polymorphism的区别在于:不涉及virtual(如virtual继承等),并且在编译期间(compilation)就完成所有调用。

现在用一个方法getValue来构建一个CRTP基类:

template <typename T>
class Amount {
public:
    double getValue() const {
        return static_cast<T const&>(*this).getValue();
    }
};

现在这个接口有两个实现:一个总是返回常量,一个可以设置值。 这两个实现继承自CRTP Amount基类:

class Constant42 : public Amount<Constant42> {
public:
    double getValue() const {return 42;}
};

class Variable : public Amount<Variable> {
public:
    explicit Variable(int value) : value_(value) {}
    double getValue() const {return value_;}
private:
    int value_;
};

最后,为interface创建一个打印函数:

template<typename T>
void print(Amount<T> const& amount) {
    std::cout << amount.getValue() << '\n';
}

可以使用以下两种实现之一调用该函数:

Constant42 c42;
print(c42);
Variable v(43);
print(v);
///    42
///43

需要注意的是,尽管Amount类是以多态方式进行使用的,但代码中没有virtual。这就意味着多态调用(polymorphic call )已经在编译期完成,从而避免了虚函数(virtual function)的运行时(run-time)成本。从设计的角度看,能够避免virtual calls的原因是:我们可以在编译期获得Amount类的信息。

去除static_cast

在CRTP基类中编写冗余的static_casts会变得很麻烦,因为它不会给代码带来太多意义:

template <typename T>
struct NumericalFunctions {
    void scale(double multiplicator) {
        T& underlying = static_cast<T&>(*this);
        underlying.setValue(underlying.getValue() * multiplicator);
    }
    ...
};

static_casts去除后将使得代码更紧凑。不过为了实现相同的效果,可以通过将基础类型forwarding到更高层次结构级别(higher hierarchy level)来实现:

template <typename T>
struct crtp {
    T& underlying() { return static_cast<T&>(*this); }
    T const& underlying() const { return static_cast<T const&>(*this); }
};

另外,它处理的是底层对象是const的情况,我们还没有提到过。可以通过以下方式使用此helper:

template <typename T>
struct NumericalFunctions : crtp<T> {
    void scale(double multiplicator) {
        this->underlying().setValue(this->underlying().getValue() * multiplicator);
    }
    ...
};

Note that the static_cast is gone and a this-> appeared. Without it the code would not compile. Indeed, the compiler is not sure where underlying is declared. Even if it is declared in the template class crtp, in theory nothing guarantees that this template class won’t be specialized and rewritten on a particular type, that would not expose an underlying method. For that reason, names in template base classes are ignored in C++.

Adding several functionalities with CRTP

为了演示,将CRTP类拆分为两个:一个用于scale values,另一个用于squares values:

template <typename T>
struct Scale : crtp<T> {
    void scale(double multiplicator) {
        this->underlying().setValue(this->underlying().getValue() * multiplicator);
    }
};

template <typename T>
struct Square : crtp<T> {
    void square() {
        this->underlying().setValue(this->underlying().getValue() * this->underlying().getValue());
    }
};

并将这两个功能添加到Sensitivity类:

class Sensitivity : public Scale<Sensitivity>, public Square<Sensitivity> {
public:
    double getValue() const { return value_; }
    void setValue(double value) { value_ = value; }
private:
    double value_;
};

乍一看还不错,但是一旦调用其中一个基类方法就会出现编译错误!

error: crtp<Sensitivity>is an ambiguous base of Sensitivity

原因是出现了钻石继承(diamond inheritance):


另一种方法是通过让每个functionality(scale,square)继承自己的crtp类来避开钻石继承。这可以通过CRTP来实现。

实际上,可以在crtp类中添加一个与对基类相对应的的template parameter,此外需要注意crtpType模板参数的添加。

template <typename T, template<typename> class crtpType>
struct crtp {
    T& underlying() { return static_cast<T&>(*this); }
    T const& underlying() const { return static_cast<T const&>(*this); }
private:
    crtp(){}
    friend crtpType<T>;
};

【注意】:crtpType是一个template template parameter,;例如它的值可以是Scale

类层次结构现在如下所示:

CRTP是一种静态多态(static polymorphism/Static binding/Compile-Time binding),与其对应的是动态多态(dynamic polymorphism/Dynamic binding/Run-Time binding)。

静态多态与和动态多态的区别是:

  • traditional多态是动态绑定(或运行时绑定,run-time binding)
  • CRTP是静态绑定(编译时绑定,compile-time binding)。

其中,动态多态在实现多态时,需要重写虚函数(virtual function),这种运行时绑定的操作往往需要查找虚表等,效率较低。template的核心技术在于编译期多态机制,与运行期多态(run-time polymorphism)相比,这种动态机制能提供编译期多态性,赋予程序运行期无可比拟的效率优势。如果想在编译期确定通过基类来得到派生类的行为,CRTP便是一种绝佳的选择。

此外,std::enable_shared_from_this也采用了CRTP进行实现。

std::enable_shared_from_this

定义在<memory>,其原型为:

template< class T > class enable_shared_from_this;

std::enable_shared_from_this允许由std::shared_ptr实例pt进行管理的对象t可以安全地生成额外的std::shared_ptr实例pt1,pt2,...,这些实例与pt共享t的所有权(ownership)。

公开继承std::enable_shared_from_this<T>时,将会为类型T提供一个member function:shared_from_this。 如果T类型的对象tstd::shared_ptr<T>实例pt进行管理,则调用T::shared_from_this将返回一个新的std::shared_ptr<T>,它将与pt共享t的所有权。

shared_from_this

返回一个std::shared_ptr<T>,它与现存的所有std::shared_ptr对象共享*this的所有权。

例子:

struct Foo : public std::enable_shared_from_this<Foo> {
    Foo() { std::cout << "Foo::Foo\n"; }
    ~Foo() { std::cout << "Foo::~Foo\n"; } 
    std::shared_ptr<Foo> getFoo() { return shared_from_this(); }
};

int main() {
    Foo *f = new Foo();
    std::shared_ptr<Foo> p1;

    {
        std::shared_ptr<Foo> p2(f);
        p1 = p2->getFoo();  // shares ownership of object with p2
    }

    std::cout << "p2 is gone\n";   
}

///    Foo::Foo
///    p2 is gone
//    Foo::~Foo

扩展:

Functor

functor是一个定义了operator()的类, 这便可以创建一个“看起来像”函数的对象:

// this is a functor
struct add_x {
  add_x(int x) : x(x) { }
  int operator()(int y) const { return x + y; }

private:
  int x;
};

// Now you can use it like this:
add_x add42(42);     // create an instance of the functor class
int i = add42(8);         // and "call" it
assert(i == 50);         // and it added 42 to its argument

std::vector<int> in;   // assume this contains a bunch of values)
std::vector<int> out(in.size());
// Pass a functor to std::transform, which calls the functor on every element 
// in the input sequence, and stores the result to the output sequence
std::transform(in.begin(), in.end(), out.begin(), add_x(1)); 
///    why write there add_x, not the add42?
///    Both would have worked (but the effect would have been different). If I'd used add42, I would have used the functor I created earlier, and added 42 to each value. With add_x(1) I create a new instance of the functor, one which only adds 1 to each value. It is simply to show that often, you instantiate the functor "on the fly", when you need it, rather than creating it first, and keeping it around before you actually use it for anything
assert(out[i] == in[i] + 1);     // for all i

仿函数有几个好处:一个是与regular function不同,它们可以包含状态(state)。上面的示例中创建了一个函数,它可以将user提供的任何内容加上42。但是,值42不是hardcoded(”Hard Coding” means something that you want to embeded with your program or any project that can not be changed directly),当创建functor实例时,它被指定为构造函数(ctor)参数。此外,可以通过不同的值(调用构造函数)来创建加上27的adder。这使得user的可以很好地根据自己的需求来进行定制。

如最后一行所示,经常将functor作为参数传递给其他function(例如std::transform或其他STL algo)。此外,regular function pointer可以执行相同的操作,但functor可以进行customize,因为它们包含状态,使得它们更加灵活(如果想要使用function pointer,就必须编写一个function,并在它的参数中明确地加上1。functor更加general,只要在在初始化时添加了试图操作的value即可)。在上面的例子中,编译器确切地知道std::transform应该调用哪个函数。它应该调用add_x::operator(),这意味着此functor可以内联该函数调用。这如同为vector的每一个值手动调用函数样高效。

【注意】:Functor必须拥有operator(), 因为caller就依靠它来进行invoke。至于是否必须拥有member functions, constructors, operators and member variables都完全取决于user。 (在functional programming中,定义了operator()的function(它不是class)属于functor,但在C++中却不是,因为在C++中的定义是:the functor is specifically a class used as a function)。

RAII(Resource Acquisition Is Initialization )

RAII, is a C++ programming technique which binds the life cycle of a resource that must be acquired before use (allocated heap memory, thread of execution, open socket, open file, locked mutex, disk space, database connection—anything that exists in limited supply) to the lifetime of an object.

C++ RAII的通用形式:

class someResource {
    //internal representation holding pointers, handles etc.

public:
    someResource() {
        //Obtain resource.
    }

    ~someResource() {
    //Release resource.
    }

};

总结:

  • 将每个资源封装入一个类
    • 构造函数请求资源,并创建类的所有invariants(常量)或者在它无法完成时抛出异常
    • 析构函数释放资源并,而且绝不会抛出异常
  • 通过RAII类实例进行使用的资源的特性为:
    • 该资源自身拥有automatic storage duration或temporary lifetime(临时生存期),或
    • 该资源的lifetime与自动(automatic)对象或临时对象(temporary object)绑定

移动语义使得在维护资源安全的同时,在对象间、跨作用域以及线程内外移动安全移动所有权成为可能。

C++标准库遵循RAII来管理其自身的资源:std::stringstd::vectorstd::thread,以及多数其他类在构造函数(错误时抛出异常)中获取其资源,并在其析构函数(决不抛出)中释放之,且不要求显式清理。

另外,标准库提供几种RAII wrappwer用于管理用户提供的资源:

  • std::unique_ptrstd::shared_ptr用于管理动态分配的内存或用普通指针表示的资源;
  • std::lock_guardstd::unique_lockstd::shared_lock用于管理互斥。

例子:使用RAII阻止内存泄漏
泄漏通常是不可接受的。 手动释放资源容易出错,RAII是防止泄漏的最简单也最系统的方式。

void f1(int i) {          // Bad: possibly leak
    int* p = new int[12];
    // ...
    if (i < 17) throw Bad{"in f()", i};
    // ...
}

可以在throw之前小心地释放资源:

oid f2(int i)   {        // 笨拙且容易出错: explicit release
    int* p = new int[12];
    // ...
    if (i < 17) {
        delete[] p;
        throw Bad{"in f()", i};
    }
    // ...
}

上述代码显得十分冗余, 在具有多个可能throw的较大代码中,explicit版本变得重复且容易出错。

void f3(int i) {`  // OK: resource management done by a handles
    auto p = make_unique<int[]>(12);
    // ...
    if (i < 17) throw Bad{"in f()", i};
    // ...
}

注意,即使throw是隐式的,因为它发生在被调用的函数中,这也是有效的:

void f4(int i)  {    // OK: resource management done by a handle 
    auto p = make_unique<int[]>(12);
    // ...
    helper(i);   // may throw
    // ...
}

除非确实需要指针语义(pointer semantics),否则请使用本地资源对象(local resource object):

void f5(int i)  {      // OK: resource management done by local object
    vector<int> v(12);
    // ...
    helper(i);   // may throw
    // ...
}

这更简单,更安全,而且效率更高。

扩展阅读:

  1. The RAII Programming Idiom
  2. What is meant by Resource Acquisition is Initialization (RAII)?

    static_cast VS const_static VS dynamic_cast VS reinterpret_cast

static_cast

static_cast的行为类似类型之间的隐式转换(例如从intfloat或指向void*类型的指针),它还可以调用显式转换函数(或隐式转换函数)。 在许多情况下,明确声明static_cast不是必需的,但要注意T(something)语法等同于(T)somthing,并且应该避免这样做。 但是,T(something,something_else)是安全的,并且保证可以调用构造函数。

static_cast也可以通过继承体系进行转换。 向上转型时(即向base class转型)时是不必要的,但向下转型时,只要不是通过virtual inheritance进行强制转换就可以使用它。 It does not do checking, however, and it is undefined behavior to static_cast down a hierarchy to a type that isn’t actually the type of the object.

const_cast

const_cast主要是用于在有不同cv-qualified的类型之间进行转换,去除或者添加const修饰符。

const_cast < new_type > ( expression );。仅适用于以下4种情况:

  1. 指向同一类型的两个多级指针可以相互转换(不管每一级有多少cv-qualifier)
  2. 任意类型T的lvalue可以被转换为T类型的lvalue or rvalue reference。同样的,一个rvalue可以被转换为rvalue reference
  3. null pointer value可以被转换为值为new_type的null pointer value
    【注】:const_cast对函数指针和成员函数指针都不起作用。

例子:

struct type {
    type() :i(3) {}
    void m1(int v) const {
        // this->i = v;                 // 编译错误:这是指向 const 的指针
        const_cast<type*>(this)->i = v; // 只要对象不是 const 就 OK
    }
    int i;
};

int main() {
    int i = 3;                    // i 不声明为 const
    const int& cref_i = i; 
    const_cast<int&>(cref_i) = 4; // OK :修改 i
    std::cout << "i = " << i << '\n';

    type t; // note, if this is const type t;, then t.m1(4); is UB
    t.m1(4);
    std::cout << "type::i = " << t.i << '\n';

    const int j = 3; // j 声明为 const
    int* pj = const_cast<int*>(&j);
    // *pj = 4;         // 未定义行为!

    void (type::*mfp)(int) const = &type::m1; // 指向成员函数指针
//  const_cast<void(type::*)(int)>(mfp); // 编译错误: const_cast 对函数指针不起作用
}
/// i=4
///type::i=4

dynamic_cast

dynamic_cast < new_type > ( expression )

dynamic_cast主要用于处理多态性。可以使用它而不仅仅是向下转型,也可以向上或者侧向转换到类的指针或引用。

  • 若转型成功,则返回new_type类型的值
  • 若转型失败且new_type是指针类型,则它返回该类型的空指针
  • 若转型失败且new_type是引用类型,则它抛出匹配类型 异常(std::bad_cast

仅下列转换适用于dynamic_cast

  1. 如果expression的类型恰好是new_typenew_type的较少cv-qualified版本,则返回new_type类型的expression(换言之,dynamic_cast可用以添加constness。隐式转型和static_cast也能进行此转换。)
  2. 如果expression是空指针值,则返回new_type类型的空指针值。
  3. 如果new_typeBase的指针或引用(即基类指针或者引用),且expressionDerived类型的指针或引用(即派生类指针或者引用),其中BaseDerived的单一可访问基类,则返回Base类子对象的指针或引用(注意:隐式转型和static_cast也能进行此转换)
  4. 如果expression是多态类型的引用或指针,且new_typevoid指针,则返回expression所指向或引用对象的最终派生类的指针。
  5. expression是多态类型Base的指针或引用,且 new_typeDerived类型的指针或引用,则进行run-time检查:
    • a) The most derived object pointed/identified by expression is examined. If, in that object, expression points/refers to a public base of Derived, and if only one subobject of Derived type is derived from the subobject pointed/identified by expression, then the result of the cast points/refers to that Derived subobject. (This is known as a downcast)
    • b) Otherwise, if expression points/refers to a public base of the most derived object, and, simultaneously, the most derived object has an unambiguous public base class of type Derived, the result of the cast points/refers to that Derived (This is known as a sidecast)
    • c) Otherwise, the runtime check fails. If the dynamic_cast is used on pointers, the null pointer value of type new_type is returned. If it was used on references, the exception std::bad_cast is thrown.
      6) dynamic_cast用于构造函数或析构函数时(直接或间接),且expression指向正在构造/析构的对象时,该对象被认为是最终派生对象。如果new_type不是到构造函数/析构函数自身的类或其基类之一的指针,则行为未定义。

例子

struct V {
    virtual void f() {};  // must be polymorphic to use runtime-checked dynamic_cast
};
struct A : virtual V {};
struct B : virtual V {
  B(V* v, A* a) {
    // casts during construction (see the call in the constructor of D below)
    dynamic_cast<B*>(v); // well-defined: v of type V*, V base of B, results in B*
    dynamic_cast<B*>(a); // undefined behavior: a has type A*, A not a base of B
  }
};
struct D : A, B {
    D() : B((A*)this, this) { }
};

struct Base {
    virtual ~Base() {}
};

struct Derived: Base {
    virtual void name() {}
};

int main()
{
    D d; // the most derived object
    A& a = d; // upcast, dynamic_cast may be used, but unnecessary
    D& new_d = dynamic_cast<D&>(a); // downcast
    B& new_b = dynamic_cast<B&>(a); // sidecast


    Base* b1 = new Base;
    if(Derived* d = dynamic_cast<Derived*>(b1))
    {
        std::cout << "downcast from b1 to d successful\n";
        d->name(); // safe to call
    }

    Base* b2 = new Derived;
    if(Derived* d = dynamic_cast<Derived*>(b2))
    {
        std::cout << "downcast from b2 to d successful\n";
        d->name(); // safe to call
    }

    delete b1;
    delete b2;
}
///downcast from b2 to d successful

copy-and-swap idiom

1. 为什么需要copy-and-swap idiom

任何管理资源的类(即wrapper,例如智能指针)都需要实现The big Three。 虽然拷贝构造函数(copy ctor)和析构函数(dtor)的作用和实现都很简单,但拷贝赋值运算符(copy assignment operator)可以说是最微妙和最困难的。

copy-and-swap idiom是解决方案,可以帮助赋值操作符(assignment operator)完美地实现两件事:避免代码重复,并提供强大的异常保证

2. 工作机制

从概念上讲,它通过使用拷贝构造函数(copy ctor)创建数据的本地副本,然后使用swap函数获取拷贝的数据,利用新数据swap旧数据。最后销毁临时拷贝,并用它来获取旧数据。我们留下了新数据的副本。

为了使用copy-and-swap idiom,需要三件事:拷贝构造函数,析构函数以及一个swap函数

swap函数是一种nonp-throwing函数,它交换类的两个对象以及各自的成员。我们可能想要使用std::swap而不是提供我们自己的,但这是不可能的。std::swap在其实现中使用了copy ctor和copy-assignment operator,我们只需根据自身需求定义赋值运算符(assignment operator)。

(不仅如此,但对swap的不合格调用将使用我们的自定义交换运算符,跳过std :: swap所需的不必要的构造和类的破坏。)

3. 深入理解

考虑一个具体案例,现试图在一个类中管理一个动态数组。 先从一个构造函数,拷贝构造函数和析构函数开始:

#include <algorithm> // std::copy
#include <cstddef> // std::size_t

class dumb_array {
public:
    // (default) constructor
    dumb_array(std::size_t size = 0)
        : mSize(size),
          mArray(mSize ? new int[mSize]() : nullptr) { }

    // copy-constructor
    dumb_array(const dumb_array& other)
        : mSize(other.mSize),
          mArray(mSize ? new int[mSize] : nullptr) {
        // note that this is non-throwing, because of the data
        // types being used; more attention to detail with regards
        // to exceptions must be given in a more general case, however
        std::copy(other.mArray, other.mArray + mSize, mArray);
    }

    // destructor
    ~dumb_array() {
        delete [] mArray;
    }

private:
    std::size_t mSize;
    int* mArray;
};

这个类基本上可以管理数组了,但它还需要operator =才能正常工作。

现在有一个naive的实现:

// the hard part
dumb_array& operator=(const dumb_array& other) {
    if (this != &other) {// (1)
        // get rid of the old data...
        delete [] mArray; // (2)
        mArray = nullptr; // (2) *(see footnote for rationale)

        // ...and put in the new
        mSize = other.mSize; // (3)
        mArray = mSize ? new int[mSize] : nullptr; // (3)
        std::copy(other.mArray, other.mArray + mSize, mArray); // (3)
    }

    return *this;
}

现在成功地管理一个数组,并且没有内存泄漏。但是,它存在三个问题,在代码中已用数字标出。

  1. 首先是self-assignment test。这个检查有两个目的:利用简单的方法来阻止在自我赋值上运行不必要的代码;可以免受微妙的错误(例如删除数组只是为了尝试复制它)。但在其他情况下,它只会降低程序的运行速度,并会冗余代码;self-assignment(自我赋值)很少发生,因此在大多数的情况在,这种检查是浪费。如果没有它, 可以正常工作会更好。
  2. 第二是它只提供基本的异常(exception)保证。如果new int [mSize]失败,*this将被修改(即size错误,数据都将遗失)。而对于强大的异常保证,它需要类似于:

    dumb_array&operator =(const dumb_array&other){
     if(this!=&other){//(1)  
         //在替换旧数据之前应先准备好新数据
         std :: size_t newSize = other.mSize;
         int * newArray = newSize? new int [newSize]():nullptr; //(3)
         std :: copy(other.mArray,other.mArray + newSize,newArray); //(3)
    
         //替换旧数据(都是non-throwing)
         delete[] mArray;
         mSize = newSize;
         mArray = newArray;
     }
    
     return *this;
    }
    
  3. 代码快速膨胀导致了第三个问题:代码重复。我们的赋值运算符(assignment operator)拷贝了我们已经在其他地方写过的所有代码,这是一件非常糟糕的事情。

在上述的例子中,它的核心只有两行(allocation和copy),但是对于更复杂的资源,代码膨胀问题可能非常麻烦。我们应该精良不要重复自己。

正确的做法

copy-and-swap idiom可以解决这些问题,但现在还需要一个swap函数。 虽然The Rule of Three可以确保我们的拷贝构造函数(copy ctor),赋值运算符(assignment operator)和析构函数(dtor)存在,但它应该被称为The Big Three and A Half:只要你的类需要管理资源,就应该提供交换一个swap函数。

现需要在我们的类中添加swap功能:

class dumb_array {
public:
    // ...

    friend void swap(dumb_array& first, dumb_array& second) {// nothrow
        // enable ADL (not necessary in our case, but good practice)
        using std::swap;

        // by swapping the members of two objects,
        // the two objects are effectively swapped
        swap(first.mSize, second.mSize);
        swap(first.mArray, second.mArray);
    }

    // ...
};

现在我们不仅可以swap我们的dumb_array,而且swap更有效率。 它只是swap pointers和sizes,而不是allocate和copy整个数组。 除了功能和效率方面的优势,也为copy-and-swap idiom的实现做好了铺垫。

赋值运算符的实现为:

dumb_array& operator=(dumb_array other) {// (1)
    swap(*this, other); // (2)

    return *this;
}

工作原理

首先注意到一个特征:参数是以值传递的方式。 虽然可以轻松地实现以下操作:

dumb_array& operator=(const dumb_array& other) {
    dumb_array temp(other);
    swap(*this, temp);

    return *this;
}

无论哪种方式,这种获取资源的方法是消除代码重复的关键:可以使用copy-constructor中的代码来创建副本,而不需重复它。现在副本已经创建完成,便已准备好进行swap。

注意,在进入该函数时,新数据已经allocate,copy完成,可随时使用。这就提供强有力的异常保证:如果副本创建失败,将不会进入swap函数,因此也不可能改变*this的状态。

将当前数据与复制的数据进行swap,安全地改变我们的状态,并将旧数据放入临时数据中,然后在函数返回时释放旧数据。

因为idiom不重复代码,所以不能在运算符(operator)中引入错误。这意味着我们不需要进行自我赋值检查(self-assignment check),从而允许只实现一个operator =

这就是copy-and-swap idiom。

其他解释:

assignment的两个主要步骤:拆除对象的旧状态,并将其新状态构建为其他对象状态的副本。
基本上,这就是析构函数和拷贝构造函数的作用,因此第一个想法是将工作委托给它们。然而,由于析构(destruction)必定不会失败,而构造(construction)可能,我们实际上想要反其道而行之:首先执行construction,如果成功,再进行destruction。copy-and-swap idiom是这样做的:它首先调用类的拷贝构造函数来创建temporary,然后用temporary交换数据,然后让temporary的析构函数(dtor)销毁旧状态。
由于swap()永远不会失败,因此唯一可能失败的部分是拷贝构造的时候。首先执行此操作,如果失败,则目标对象不会更改任何内容。

constconstexpr的区别

constexpr的主要用处:

  • 拓宽常量表达式的范围
  • 提供显示要求表达式编译时(compile-time)求值的方法

拓宽常量表达式的范围的理由:
例如INT_MAX是C语言的遗物,C++更希望大家使用std::numeric_limits<int>::max()作为int的上限,然而它是个函数调用而不是常量,使用起来可能需要考虑更多,没有前者那么易用。

例如标准文件流,它的构造函数可以如下使用:std::fstream foo("foo.txt", std::ioss::in | std::ios::out);。这个参数是openmode类型,由实现具体定义的一种bitmask类型。出于类型安全的考虑,通常使用枚举值实现而不是整形。但这样会造成一个问题,同样是写std::ios::in|std::ios|out,如果用整型的话可以作为常量表达式使用,而为了类型安全考虑换用枚举实现(尤其是重载 | 操作符)后,就再也不可能是常量表达式了。

inline openmode operator|(openmode lhs, openmode rhs) {
    return openmode(int_type(lhs) | int_type(rhs));
}

虽然是简单的函数,但对它的调用却不是常量表达式。这就让委员会陷入了必须在类型安全和效率中二选一的境地。标准委员会正好借此机会将原本标准中对于常量表达式(尤其是整形常量表达式)复杂的定义重构简化,引入constexpr便合情合理。

注意,constexpr函数并不能个编译时求值划等号。,它只是表达了这个函数具备这样的能力。所以才有了constexpr的第二个功能:显示要求表达式在编译时进行求值。把它放到变量定义前,那么用来初始化这个变量的表达式必须是常量表达式,否则将会报错。

constexpr函数只有同时满足一下条件时才会触发编译期求值(如果只有参数是常量表达式而结果不是,那么是否会触发编译时求值则取决于具体实现):

  • 所有参数都是常量表达式
  • 返回的结果被用于常量表达式(比如用于初始化constexpr数据)

constexpr的常见应用场景(一般在模板元编程中):
1. 简化元函数:

template <typename U, typename V>
constexpr bool is_same_v = std::is_same<U, V>::value;

可以直接这样auto x = is_same_v<int, float>;进行调用。

2. 用于元编程的常量成员

template <typename U, typename V>
struct is_same {
    static constexpr int value = false;
}

template <typename T>
struct is_same<T, T> {
    static constexpr int value = true;
}

便可以通过is_same<X, X>::value;来调用这个元函数。
3. 修饰常函数

constexpr int add(int a, int b) { return a+b; }

4. 修饰分支
if constexpr () {}
其实可以将constexpr修饰的东西看成元函数的变量。
两个关键字都能用于对象和函数的声明。两者在声明对象时的主要差别为:

  • const将对象声明为常量(constant)。这就意味着一旦被初始化,则对象的值将不再改变,并且编译器可以利用这一事实进行优化。从外,它还能防止程序员修改某些不能再被修改的对象。
  • constexpr声明一个对象,使得该对象可以在标准中称为的constant expression中使用。

两者在声明函数时的主要差别为:

  • const只能用于非静态成员函数(non-static member function),而并不是所有的函数。其确保成员函数不会修改任何非静态数据成员。
  • constexpr能同时用于成员函数和非成员函数。其将函数声明为constant expression。

扩展:Difference between constexpr and const

迭代器(iterator)

插入迭代器(insert_iterator

被绑定到一个容器上,可以向容器插入元素。

  • back_inserter只要迭代器被赋值,容器内的push_back()成员函数将被调用。
  • front_inserter创建一个使用push_front的迭代器。
  • inserter创建一个使用insert的迭代器。元素将被插入到指定迭代器所表示的元素之前。

    移动迭代器(move_iterator

    不是拷贝其中的元素,而是移动它们。一个move iterator通过改变给定迭代器的dereference operator的行为来适配此迭代器。一般来说,一个迭代器的dereference operator将返回一个指向元素的左值。与其他迭代器不同,move iterator的dereference operator将返回一个右值引用。通过make_move_iterator可以将一个普通迭代器转换为一个move iterator。

其可能实现为:

template <typename Iterator>
std::move_iterator<Iterator> std::make_move_iterator(Iterator i) {
    return std::move_iterator<Iterator>(i);
}

例子:

int main() {
    std::list<std::string> s{"one", "two", "three"};

    std::vector<std::string> v1(s.begin(), s.end()); // copy

    std::vector<std::string> v2(std::make_move_iterator(s.begin()),
                                std::make_move_iterator(s.end())); // move

    std::cout << "v1 now holds: ";
    for (auto str : v1)
            std::cout << "\"" << str << "\" ";
    std::cout << "\nv2 now holds: ";
    for (auto str : v2)
            std::cout << "\"" << str << "\" ";
    std::cout << "\noriginal list now holds: ";
    for (auto str : s)
            std::cout << "\"" << str << "\" ";
    std::cout << '\n';
}

//v1 now holds: "one" "two" "three"
//v2 now holds: "one" "two" "three"
//original list now holds: "" "" ""

流迭代器(ostream_iterator

istream_iterator

读取输入流,其使用>>操作符来读取流。因此, istream_iterator要读取的类型必须定义了输入运算符。当创建一个istream_iterator时,便可以将它绑定到一个流。

例子:

/// demo1
std::istream_iterator<int> int_iter(std::cin);        //从cin读取int
std::istream_iterator<int> in_eof;            //尾后迭代器
std::ifstream in("afile");
std::istream_iterator<std::string> str_iter(in);    //从afile读取字符串

///demo2
int main() {
    std::istringstream stream("1 2 3 4 5");
    std::copy(
        std::istream_iterator<int>(stream),
        std::istream_iterator<int>(),
        std::ostream_iterator<int>(std::cout, " ")
    );
}
// 1 2 3 4 5

///demo3  用istream_iterator从标准输入读取数据,存入vector
std::istream_iterator<int> in_iter(std::cin);
std::istream_iterator<int> eof;
while(in_iter != eof) {
    vec.push_back(*in_iter++);
}

ostream_iterator

构造函数为:

///以stream为关联流(associated stream),并且以delim作为分隔符(delimiter)
ostream_iterator(ostream_type& stream, const CharT* delim);
///以stream为关联流(associated stream),并且以空指针(null pointer)作为分隔符(delimiter)
ostream_iterator(ostream_type& stream);

例子:

int main(){
    std::istringstream str("0.1 0.2 0.3 0.4");
    std::partial_sum(std::istream_iterator<double>(str),
        std::istream_iterator<double>(),
        std::ostream_iterator<double>(std::cout," "));
}
/// 0.1 0.3 0.6 1

std::partial_sum
可能实现为:

template <typename InputIt, typename OutputIt>
OutputIt partial_sum(InputIt first, InputIt last, OutputIt d_first) {
    if(first == last) return d_first;

    typename std::iterator_traits<InputIt>::value_type sum = *first;
    *d_first = sum;

    while(++first != last) {
        sum = std::move(sum) + *first;
        *++d_first = sum;
    }
    return ++d_first;
}

例子:

std::vector<int> vec = {1,2,3,4};
//partial_sum <numeric>
std::partial_sum(vec.begin(), vec.end(), std::multiplies<int>());
///1 2 6 24

variadic function

variadic function是使用可变数量参数的函数(例如printf)。它在最后一个参数后加...声明,例如 int printf(const char* format, …); 。定义在<stdarg.h>头文件中。

1. va_list

其实现为:

typedef char* va_list;

它是一个complete object,用于保存va_startva_copyva_argva_end的信息。

若创建一个va_list实例,并将其传递给另一个函数,则在该函数中需要通过va_arg来使用它。此外,在此调用方函数中的任何后继调用必须继续调用va_end

2. va_start

void va_start( va_list ap, parmN );

使函数能访问参数parmN之后的可变参数。在任何对va_arg的调用前,需要使用合法的va_list对象ap调用va_start。其实现为:

#define va_list(list,param1) (list=(va_list)&param1+sizeof(param1))

【注】其中路list是类型为va_list的指针,param1是可变参数最左边的参数

4. va_arg

T va_arg( va_list ap, T );

它将展开成T类型的表达式(此表达式对应va_list ap的下个参数)。

调用va_arg前,必须调用va_startva_copy初始化ap,中间不能有va_end调用。每次调用va_arg宏都会修改ap,令它指向下一个可变参数。

其实现为:

#define va_arg(list,mode) ((mode*)(list+=sizeof(mode)))[-1]

4. va_end

清空va_list或许var_copy进行初始化的可变参数列表。

可能实现为:

#define va_end(list) (list=(va_list)0)

【注】:以上sizeof()只是为了说明工作原理,实际实现中,增加的字节数需要保证为int的整数倍。例如:

#define _INTSIZEOF(n) ((SIZEOF(n)+sizeof(int)-1)&~(sizeof(int)-1))

最复杂的宏是va_arg,它必须返回一个由va_list所指向的恰当的类型的数值,同时递增va_list,使它指向参数列表中的一个参数(即递增的大小等于va_arg宏所返回的数值具有相同类型的对象的长度)。因为类型转换的结果不能作为赋值运算的目标,所以va_arg宏首先使用sizeof来确定需要递增的大小,然后把它直接加到va_list上,这样得到的指针再转换为要求的类型。因为该指针现在指向的“过”了一个类型单位的大小,所以使用下标-1来存取正确的返回参数。

例子:

/// demo1
int add_nums(int count,...){
    int result=0;
    va_list args;
    va_start(args,count);
    for(int i=0;i<count;i++){
        result+=va_arg(args,int);
    }
    va_end(args);
    return result;
}

int main(){
    std::cout<<add_nums(4,1,2,3,4)<<std::endl;
}
///    10

///demo2:定制错误打印函数error
void error(char* format, ...) {
    va_list al;
    va_start(al, format);
    fprintf(stderr, "error: ");
    vfprintf(stderr, format, al);
    va_end(al);
    fprintf(stderr, "\n");
    return;
}

variadic template

对于variadic template,我们无法直接获取参数包(parameter pack)中的每个参数,只能通过展开(expand)参数包的方式,来获取参数包中的每个参数,这就是使用variadic template的主要特征,但这也是最大的难点:如何展开variadic template parameter

展开variadic template function有两种方法:

  • 通过递归函数来展开参数包
  • 通过逗号表达式(comma expression)来展开参数包

通过递归函数来展开参数包

使用这种方法时,需要提供一个参数包展开函数和一个递归终止函数(用于终止递归)

例子:

/// Recursive termination function
void print() {
    std::cout<<"empty"<<std::endl;
}

///    expansion function
template <typename T, typename...Args>
void print(T head, Args...args) {
    std::cout<<"parameter "<<head<<std::endl;
    print(args...);
}

int main() {
    print(1,2,3,4);
}

///    parameter 1
///    parameter 2
///    parameter 3
///    parameter 4
///    empty

递归调用的过程为:

print(1,2,3,4);
print(2,3,4);
print(3,4);
print(4);
print();

例子:

/// 用于求和
template <typename T>
T sum(T t) {
    return t;
}

template <typename T, typename...Args>
T sum(T head, Args...args) {
    return head+sum<T>(args...);
}

int main() {
    std::cout<<sum(1,2,3,4)<<std::endl;
}

///    10

逗号表达式展开参数包

递归函数展开参数包是一种标准做法,也比较好理解,但也有一个缺点:必须要一个重载的递归终止函数(即必须要有一个同名的终止函数来终止递归,这样可能会造成不便)。借助逗号表达式和初始化列表可以解决这一问题。

例子:

///    用comma expression改写之前的print
template <class T>
void print(T t) {
   std::cout << t << std::endl;
}

template <class ...Args>
void expand(Args... args) {
   int arr[] = {(print(args), 0)...};
}

int main() {
    expand(1,2,3,4);
}
///    1
///    2
///    3
///    4

这种展开参数包的方式,不需要通过递归终止函数,而是直接在expand函数体中展开,print不再是一个递归终止函数,而只是一个处理参数包中每一个参数的函数。这种方式的关键是逗号表达式(comma expression)。逗号表达式会按顺序执行逗号前面的表达式,比如:

d = (a = b, c); 

这个表达式会按顺序执行为:b会先赋值给a,接着括号中的逗号表达式返回c的值,因此d将等于c

expand中的逗号表达式(print(args), 0)也是按照这个顺序执行。先执行print(args),再根据逗号表达式的特性,得到结果0。此外还用到了C++11的initializer list特性。

通过initializer list来初始化一个变长数组,{(print(args), 0)...}将会展开成((print(arg1),0), (print(arg2),0), (print(arg3),0), ... )。最终会创建一个元素值都为0的数组:int arr[sizeof...(Args)]。由于是逗号表达式,在创建数组的过程中,首先会执行逗号表达式中的print(args),打印出参数(即在构造int数组的过程中就已经将参数包展开了,这个数组是为了在数组构造的过程中展开参数包)。

将上面的例子改进一下:将函数作为参数,便可以支持lambda expression,从而可以少写一个递归终止函数。

template <typename F, typename...Args>
void expand(const F& f, Args...args) {
    std::initializer_list<int>{(f(std::forward<Args>(args)),0)...};
}
int main() {
    expand([](int i) {
        std::cout<<i<<std::endl;
    },1,2,3);
}
///    1
///    2
///    3

如果使用C++14的generic lambda,则可以写更泛化的lambda表达式:

expand([](auto i) {
    std::cout<<i<<std::endl;
}, 1,2.0,”test”);

variadic template class

variadic template class的展开(expansion)一般需要以下几个declaration:类声明(class declaration)和偏特化模板类(partial template specialization)。

例子:

///此demo用于在编译期计算parameter pack中参数类型的size之和

///    forwarding declaration
/// 声明Sum类是一个variadic template class
template <typename...Args>
struct Sum;

///    primary template definition
/// 定义了一个部分展开的variadic template class,它告诉编译器如何递归地展开parameter pack
template <typename Head, typename...Rest>
struct Sum<Head, Rest...> {
    enum {
        value=Sum<Head>::value+Sum<Rest...>::value
    };
};

/// terminate the recursion
template <typename Tail>
struct Sum<Tail> {
    enum {
        value=sizeof(Tail)
    };
};

int main(){
    std::cout<<Sum<int,int,int>::value<<std::endl;
}

其实可以将上述的forwarding declaration进行更改,现在要求Sum的模板参数至少有一个,因为variadic template class的模板参数可以为0个(有时候0个模板参数没有意义),于是就可以通过下面的声明方式,来限定模板参数不能为0个。上面的这种三段式的定义也可以改为两段式的,可以将forwarding declaration去除:

template<typename Head, typename... Rest>
struct Sum {
    enum { 
        value = Sum<Head>::value + Sum<Rest...>::value 
    };
};

template<typename Tail>
struct Sum<Tail> {
    enum { 
        value = sizeof(Tail) 
    };
};

递归终止模板类可以有多种写法,比如上例的递归终止模板类还可以这样写:

template<typename... Args> struct Sum;

template<typename Head, typename Tail>
struct Sum<Head, Tail> { 
    enum { 
        //在展开到最后两个参数时终止
        value = sizeof(First) +sizeof(Last) 
    };
};

在展开到0个参数时终止:

template<>
struct Sum<> { 
    enum{ value = 0 }; 
};

可以使用std::integral_constant来消除枚举定义value。由于std::integral_constant可以获得编译期常量,可以将之前的Sum例子进行改写:

///    forwarding decla
template<typename Head, typename... Args>
struct Sum;

///    primary template class
template<typename Head, typename... Rest>
struct Sum<Head, Rest...> : std::integral_constant<int, Sum<Head>::value + Sum<Rest...>::value> { };

///    terminate the recursion
template<typename Tail>
struct Sum<Tail> : std::integral_constant<int, sizeof(Tail)> { };

通过继承方式展开参数包

例子:

/// 整形序列的定义
template<int...>
struct index_sequence { };

//通过继承方式,开始展开参数包
template<int N, int... Indices>
struct make_index : make_index<N - 1, N - 1, Indices...> { };

// 模板特化,终止展开参数包的条件
template<int... Indices>
struct make_index<0, Indices...> {
    typedef index_sequence<Indices...> type;
};

int main() {
    using T = make_index<3>::type;
    std::cout <<typeid(T).name() << std::endl;
}
///    struct index_sequence<0,1,2>

其中make_index的作用是为了生成一个variadic template class的整数序列。

make_index继承于自身的一个特化模板(template specialization),此特化模板同时也在展开参数包,这个展开过程是通过继承发起的,直到遇到特化的终止条件,展开过程才结束。

make_index<1,2,3>::type的展开过程是这样的:

make_index<3> : make_index<2, 2>{ }
make_index<2, 2> : make_index<1, 1, 2>{ }
make_index<1, 1, 2> : make_index<0, 0, 1, 2> {
    typedef index_sequence<0, 1, 2> type;
}

通过不断的继承递归调用,最终得到整型序列index_sequence<0, 1, 2>。如果不希望通过继承方式生成整形序列,则可以通过下面的方式生成:

template<int N, int... Indices>
struct make_index {
    using type = typename make_index<N - 1, N - 1, Indices...>::type;
};

template<int... Indices>
struct make_index<0, Indices...> {
    typedef index_sequence<Indices...> type;
};

利用variadic template去除repeated code

C++11之前如果要写一个泛化的工厂函数,这个工厂函数能接受任意类型的参数,并且参数的个数要能满足大部分的应用需求的话,就不得不定义很多重复的模版定义:

template<typename T>
T* Instance() {
    return new T();
}

template<typename T, typename T0>
T* Instance(T0 arg0) {
    return new T(arg0);
}

template<typename T, typename T0, typename T1>
T* Instance(T0 arg0, T1 arg1) {
    return new T(arg0, arg1);
}

template<typename T, typename T0, typename T1, typename T2>
T* Instance(T0 arg0, T1 arg1, T2 arg2) {
    return new T(arg0, arg1, arg2);
}

template<typename T, typename T0, typename T1, typename T2, typename T3>
T* Instance(T0 arg0, T1 arg1, T2 arg2, T3 arg3) {
    return new T(arg0, arg1, arg2, arg3);
}

template<typename T, typename T0, typename T1, typename T2, typename T3, typename T4>
T* Instance(T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
    return new T(arg0, arg1, arg2, arg3, arg4);
}
struct A {
    A(int){}
};

struct B {
    B(int,double){}
};

A* pa = Instance<A>(1);
B* pb = Instance<B>(1,2);

这个泛型工厂函数存在大量的重复的模板定义,并且限定了模板参数。用variadic template可以消除重复,同时可以去掉参数数量的限制。优化后的工厂函数如下:

template<typename…  Args>
T* Instance(Args&&… args) {
    return new T(std::forward<Args>(args)…);
}

A* pa = Instance<A>(1);
B* pb = Instance<B>(1,2);

利用variadic template实现C#中的delegate

C#中的delegate的基本用法:

///    声明委托类型
delegate int AggregateDelegate(int x, int y);

int Add(int x, int y){return x+y;}
int Sub(int x, int y){return x-y;}

AggregateDelegate add = Add;
add(1,2);                                        //调用委托对象求和
AggregateDelegate sub = Sub;
sub(2,1);                                        // 调用委托对象相减

C#在使用delegate时,需要先定义一个delegate类型,这个delegate类型不能泛化(即delegate类型一旦声明之后,就不能再用来接受其它类型的函数)。例如:

int Fun(int x, int y, int z) { return x+y+z;}
int Fun1(string s, string r) { return s.Length+r.Length; }
AggregateDelegate fun = Fun;         //编译报错,只能赋值相同类型的函数
AggregateDelegate fun1 = Fun1;      //编译报错,参数类型不匹配

这里不能泛化的原因是:声明delegate类型的时候就限定了参数类型和数量。利用variadic template实现一个功能更加泛化的C++版本的delegate:

template <typename T, typename R, typename...Args>
struct delegate {
public:
    delegate(T* t, R (T::*f)(Args...))
    :m_t(t),m_f(f) {}

    R operator()(Args&&...args) {
        return (m_t->*m_f)(std::forward<Args>(args)...);
    }
private:
    T* m_t;
    R (T::*m_f)(Args...);
};

template <typename T, typename R, typename...Args>
delegate<T, R, Args...> create_delegate(T* t, R (T::*f)(Args...)) {
    return delegate<T, R, Args...>(t, f);
}

struct A {
    void Fun(int i) { std::cout<<i<<std::endl; }
    void Fun1(int i, double j) { std::cout<<i+j<<std::endl; }
};

int main() {
    A a;
    auto d1=create_delegate(&a, &A::Fun);
    d1(1);
    auto d2 = create_delegate(&a, &A::Fun1); 
    d2(1, 2.5); 
}                                              

delegate实现的关键是内部定义了一个能接受任意类型和数量参数的universal function:R (T::*m_f)(Args...)。正是由于variadic template的特性,所以才能够让m_f接受任意参数。

如何打印variadic template parameter

方法一

template <typename T>
void print(T t) {
    std::cout<< t <<std::endl;
}

template <typename T, typename...Args>
void print(T t, Args...args> {
    print(t);
    print(args...);
}

方法二

template <typename T>
void print(T t) {
    std::cout<< t <<std::endl;
}

template <typename...Args>
void print_impl(Args...args) {
    std::initializer_list<int>{ (print(args),0)... };
}

方法三

template <typename...Args>
void print(Args...args) {
    std::initializer_list<int> {
        ([&]{std::cout<< args <<std::endl;}(),0)...};
}

template <typename...Args>
void print_impl(Args...args) {
    std::initializer_list<int>{(std::cout<< args <<std::endl,0 )...};
}

int main() {
    print(1,2,3,4);
    print_impl(1,2,3,4);
}

方法四

///    当到达tuple的最后一个元素时,便什么也不做
template <std::size_t I=0, typename Tuple>
typename std::enable_if<I==std::tuple_size<Tuple>::value>::type print(Tuple t) {}

template <std::size_t I=0, typename Tuple>
typename std::enable_if<I < std::tuple_size<Tuple>::value>::type print(Tuple t) {
    std::cout<<std::get<I>(t)<<std::endl;
    print<I+1>(t);
}

template <typename...Args>
void print_impl(Args...args) {
    print(std::make_tuple(args...));
}

int main() {
    print_impl(1,3,5,7);
}

方法五

template <int N>
struct printer{
public:
    template <typename Tuple>
    static void print(const Tuple& t) {
        printer<N-1>::print(t);
        std::cout<<std::get<N-1>(t)<<std::endl;    //ascending order
    }
};

template <>
struct printer<0> {
public:
    template <typename Tuple>
    static void print(const Tuple& t) {}
};

template <typename...Args>
void print_impl(const Args...args) {
    auto p=std::make_tuple(args...);
    printer<sizeof...(args)>::print(p);
}

int main() {
    print_impl(1,2,3,4);
}

方法六

template <int...>
struct index_tuple {};

template <int N, int...Indices>
struct make_index : make_index<N-1, N-1, Indices...> {};

template <int...Indices>
struct make_index<0, Indices...> {
    typedef index_tuple<Indices...> type;
};

template <int...Indices, typename...Args>
void print_helper(index_tuple<Indices...>, std::tuple<Args...>&& t) {
    std::initializer_list<int>{ ([&]{std::cout<<std::get<Indices>(t)<<std::endl;}(), 0)...};
}

template <typename...Args>
void print(Args...args) {
    print_helper(typename make_index<sizeof...(Args)>::type(), std::make_tuple(args...));
}

int main() {
    print(0,1,2,3);
}

先将参数包转换为tuple,接着又将tuple转换为参数包。

variadic template的应用

在compile-time获取一个integer sequence中的最大值

template <std::size_t Head, std::size_t...Rest>
struct integer_sequence {};

template <std::size_t Head>
struct integer_sequence<Head> : std::integral_constant<std::size_t, Head> {};

template <std::size_t Head, std::size_t Next, std::size_t...Rest>
struct integer_sequence<Head, Next, Rest...> 
: std::integral_constant<std::size_t, 
                        Head >= Next? integer_sequence<Head, Rest...>::value:integer_sequence<Next, Rest...>::value> {};

int main() {
    std::cout<< integer_sequence<1,2,3,4>::value <<std::endl;
}

获取众多类型中的size的最大值

template <typename...Args>
struct max_align : std::integral_constant<int, 
                    integer_sequence<std::alignment_of<Args>::value...>::value> {};

std::alignment_of

对外提供类型T的alignment的member constant value,如同用alignof表达式获得。如果T是数组类型,则返回元素类型的alignment requiremet;如果T是引用类型,则返回引用所指向的元素的alignment requirement。

可能实现为:

template <typename T>
struct aligement_of : std::integral_constant< std::size_t, alignof(T)> {};

判断是否包含某个类型

template <typename T, typename...Rest>
struct Contains;

template <typename T, typename Head, typename...Rest>
struct Contains<T, Head, Rest...> 
        : std::conditional< std::is_same<T, Head>::value, 
            std::true_type, Contains<T, Rest...>>::type {};

template <typename T>
struct Contains<T> : std::false_type {};

int main() {
    std::cout<<std::boolalpha;
    std::cout<< Contains<int,short,char,int,double>::value <<std::endl;
}

获取类型的索引

template <typename T, typename...Rest>
struct index_of;

template <typename T, typename Head, typename...Rest>
struct index_of<T, Head, Rest...> {
    enum {
        value=index_of<T, Rest...>::value+1
    };
};

template <typename T, typename...Rest>
struct index_of<T, T, Rest...> {
    enum { value=0 };
};

template <typename T>
struct index_of<T> {
    enum { value=-1 };
};

在编译器遍历类型

template <typename T>
void print() {
    std::cout<< typeid(T).name() <<std::endl;
}

template <typename...Args>
void for_each() {
    std::initializer_list<int>{ (print<Args>(), 0)... };
}

function traits

function_traits用来获取函数语义的可调用对象的一些属性,比如函数类型、返回类型、函数指针类型和参数类型等。

template <typename T>
struct function_traits;

template <typename T, typename...Args>
struct function_traits<T(Args...)> {
public:
    enum { size=sizeof...(Args) };
    using return_type=T;
    typedef T function_type(Args...);  
    using stl_function_type=std::function<function_type>;
    typedef T (*pointer)(Args...);

    template <std::size_t I>
    struct argument_type {
        static_assert(I<size, "index error");
        using type=typename std::tuple_element<I, std::tuple<Args...>>::type;
    };
};

/// function pointer
template <typename T, typename...Args>
struct function_traits<T(*)(Args...)> : 
            function_traits<T(Args...)>{};

/// std::function
template <typename T, typename...Args>
struct function_traits<std::function<T(Args...)>> :
            function_traits<T(Args...)> {};

/// member function
#define FUNCTION_TRAITS(...) \
    template <typename RetrurnType, typename ClassType, typename...Args> \
    struct function_traits<RetrurnType(ClassType::*)(Args...) __VA_ARGS__>: \
            function_traits<RetrurnType(Args...)>{}; \

FUNCTION_TRAITS()
FUNCTION_TRAITS(const)
FUNCTION_TRAITS(volatile)
FUNCTION_TRAITS(const volatile)

/// function object
template <typename Callable>
struct function_traits : 
        function_traits<decltype(&Callable::operator())> {};

template <typename T>
void print() {
    std::cout<< typeid(T).name() <<std::endl;
}

int main() {
    std::function<int(int)> fun=[](int a) { return a; };
    print<function_traits<std::function<int(int)>>::function_type>();    ///int __cdecl(int)
    print<function_traits<decltype(fun)>::function_type>();                   ///int __cdecl(int)
    std::cout<<function_traits<decltype(fun)>::size<<std::endl;            ///1
}

根据索引查找类型

template <int Index, typename...Types>
struct At;

template <int Index, typename Head, typename...Types>
struct At<Index, Head, Types...> {
    using type = typename At<Index-1, Types...>::type;
};

template <typename T, typename...Types>
struct At<0, T, Types...> {
    using type = T;
};

int main() {
    using type_ = At<0,int,double,char>::type;
    std::cout<< typeid(type_).name() <<std::endl;
}

实现variant

template <typename...Types>
struct variant {
///实现variant,首先需要定义一个足够大的缓冲区用来存放不同的类型的值,这个缓类型冲区实际上是用来擦除类型的。不同的类型都通过placement new在这个缓冲区上创建对象,因为类型长度不同,所以需要考虑内存对齐。C++11提供了内存对齐的缓冲区aligned_storage。它的第一个参数是缓冲区的长度,第二个参数是缓冲区内存对齐的大小

///由于varaint可以接受多种类型,所以需要获取最大的type size,保证缓冲区足够大。然后还要获取最大的内存对齐大小,可以通过前面实现的integer_sequenece和max_align。varaint中内存对齐的缓冲区定义如下:
    enum {
        data_size = integer_sequence<sizeof(Types...)>::value;
        align_size = max_align<Types...>::value;
    };
    using data_t = typename std::aligned_storage<data_size, align_size>::type;        ///内存对齐的缓冲区类型
public:
    template <int Index>
    using index_type = typename At<Index, Types...>::type;

    variant(void):m_type_index(typeid(void)) {}
    variant(variant<Types...>&& old) : m_type_index(old.m_type_index) {
        move(old.m_type_index, &old.m_data, &m_data);
    }
    variant(const variant<Types...>& old) : m_type_index(old.m_type_index) {
        copy(old.m_type_index, &old.m_data, &m_data);
    }
    ~variant() { destroy(m_type_index,&m_data); }

///通过初始化列表和逗号表达式来展开variadic template,在展开的过程中查找对应的类型,如果找到了则直接析构。在variant构造时还需要注意,variant不能接受没有预先定义的类型。所以在构造variant时,需要限定类型必须在预定义的类型范围当中。此处通过enable_if来限定模板参数的类型。

///enbale_if的条件Contains的值,当没有在预定义的类型中找到对应的类型时,则Contains返回false,编译期会报一个编译错误。
    template <typename T,
        typename = typename std::enable_if<
            Contains <
                typename std::remove_reference<T>::type, Types...>
            ::value>
        ::type>
    variant(T&& value) : m_type_index(typeid(void)) {
        destroy(m_type_index, &m_data);
        typedef typename std::remove_reference<T>::type U;
        new(&m_data) U(std::forward<T>(value));
        m_type_index = type_index(typeid(U));
    }

    template <typename T>
    bool is() const {
        return (m_type_index == type_index(typeid(T)));
    }

    template <typename Y>
    typename std::decay<T>::type& get() {
        typedef typename std::decay<T>::type U;
        if(!is<U>()) {
            std::cout<< typeid(U).name() <<" is not defined. "
                << "current type is " << m_type_index.name() <<std::endl;
            throw std::bad_cast();
        }
        return *(U*)(&m_data);
    }

///visit的实现需要先通过定义一系列的访问函数,然后再遍历这些函数。遍历过程中,判断函数的第一个参数类型的type_index是否与当前的type_index相同,如果相同则获取当前类型的值。

///visit功能的实现利用了variadic template和function_traits通过variadic template来遍历一系列的访问函数遍历过程中,通过function_traits来获取第一个参数的类型。如果和variant当前的type_index相同的,则取值。

///为什么要获取访问函数第一个参数的类型呢?因为variant的值是唯一的,只有一个值,所以获取的访问函数的第一个参数的类型就是variant中存储的对象的实际类型。
    template <typename T, typename...Rest>
    void visit(T&& t, Rest&&...rest) {
        using Type = typename Function_traits<T>::template argument_type<0>::type;
        if(is<Type>()) {
            visit(std::forward<T>(t));
        }else {
            visit(std::forward<Rest>(rest)...);
        }
    }
private:

///还需要实现对缓冲区的构造、拷贝、析构和移动,因为variant重新赋值的时候需要将缓冲区中原来的类型析构掉,拷贝构造和移动构造时则需要拷贝和移动。以析构为例,需要根据当前的type_index来遍历variant的所有类型,找到对应的类型,然后调用该类型的析构函数。
    void destroy(const type_index& index, void* buffer) {
        std::initializer_list<int> { (destory_helper<Types>(index, buffer), 0)...};
    }

     template <typename T>
     void destory_helper(const type_index& index, void* data) {
         if(index == type_index(typeid(T))) {
             reinterpret_cast<T*>(data)-> ~T();
         }
     }

     void move(const type_index& old_t, void* old_v, void* new_v) {
         std::initializer_list<int> { (move_helper<Types>(old_t, old_v, new_v,), 0)...};
     }

     template <typename T>
     void move_helper(const type_index& old_t, void* old_v, void* new_v) {
         if(old_t == type_index(typeid(T))) {
             new (new_v) T(std::move(*reinterpret_cast<T*>(old_v)));
         }
     }

     void copy(const type_index& old_t, void* old_v, void* new_v) {
         std::initializer_list<int> { (copy_hepler<Types>(old_t, old_v, new_v), 0)...};
     }

     template <typename T>
     void copy_hepler(const type_index& old_t, void* old_v, void* new_v) {
         if(old_t == type_index(typeid(T))) {
             new (new_v) T (*reinterpret_cast<const T*>(old_v));
         }
     }

 private:
     data_t m_data;
     std::type_index m_type_index;
};

std::type_index

std::type_index是一个std::type_info的wrapper,它可作为关联容器和关联无序容器的索引。它与std::type_info对象的关系通过一个指针联系,所以它为 CopyConstructible

它有一个member function:name(),返回std::type_info对象的name。

例子:

struct A {
    virtual ~A() {}
};

struct B : A {};
struct C : A {};

int main()
{
    std::unordered_map<std::type_index, std::string> type_names;

    type_names[std::type_index(typeid(int))] = "int";
    type_names[std::type_index(typeid(double))] = "double";
    type_names[std::type_index(typeid(A))] = "A";
    type_names[std::type_index(typeid(B))] = "B";
    type_names[std::type_index(typeid(C))] = "C";

    int i;
    double d;
    A a;

    // note that we're storing pointer to type A
    std::unique_ptr<A> b(new B);
    std::unique_ptr<A> c(new C);

    std::cout << "i is " << type_names[std::type_index(typeid(i))] << '\n';
    std::cout << "d is " << type_names[std::type_index(typeid(d))] << '\n';
    std::cout << "a is " << type_names[std::type_index(typeid(a))] << '\n';
    std::cout << "b is " << type_names[std::type_index(typeid(*b))] << '\n';
    std::cout << "c is " << type_names[std::type_index(typeid(*c))] << '\n';
}
///    i is int
///    d is double
///    a is A
///    b is B
///    c is C

std::type_indexstd::type_info的区别

type_index is “a simple wrapper for type_info which can be used as an index type in associative containers (23.4) and in unordered associative containers (23.5)”. If you use type_index instead of type_info*, you will free yourself from having to provide an explicit comparator in your maps. The only cost is that you need to #include <typeindex>.

std::aligned_storage

定义在<type_traits>,其可能实现为:

template <std::size_t Len, std::size_t Align /* default alignment not implemented */>
struct aligned_storage {
    struct type {
        alignas(Align) unsigned char data[Len];
    };
};

提供一个nested type(为trivial standard-layout type),可以作为任何大小至多为Len,对齐要求为Align的对象的uninitialized storage。

Align的默认值是大小至多为Len的对象的最大对齐要求。若不使用默认值,则Align的大小为alignof(T),否则UB。若Len == 0,则UB。

例子:

template <typename T, std::size_t N>
struct static_vector {
    typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N];
    std::size_t m_size = 0;
public:
    // create an object in aligned storage
    template <typename...Args>
    void emplace_back(Args...args) {
        if(m_size >= N)    throw std::bad_alloc{};
    new(data+m_size) T(std::forward<Args>(args)...);
    ++m_size;
    }

    // acess an object in aligned storage
    const T& operator[](std::size_t pos) const {
        return *reinterpret_cast<const T*>(data+pos);
    }

    //delete objects from aligned object
    ~static_vector() {
        for(std::size_t pos=0; pos<m_size; ++pos) {
            reinterpret_cast<const T*>(data+pos)-> ~T();
        }
    }
};

int main() {
    static_vector<std::string,5> v1;
    v1.emplace_back(5, '*');
    v1.emplace_back(10, '*');
    std::cout<< v1[0] <<'\n' <<v1[1] <<'\n';
}
/// *****
/// **********

扩展:Placement new in std::aligned_storage?

std::forward<Args> or std::forward<Args...>

template <typename... Args>
void foo(Args&&... args) {
    bar(std::forward<Args>(args)...);      // #1
    bar(std::forward<Args...>(args)...);   // #2
}

在上述代码中,正确的是#1。如果参数包为空,则#1会扩展为bar(),它为valid。但#2将会扩展为bar(std::forward<>()),它为ill-formed,因为forward丢失了它的参数。

如果参数包有一个元素,则两个都会扩展为bar(std::forward<T>(t)),它们都是valid。

如果参数包有两个元素,则#1将扩展为bar(std::forward<T1>(t1), std::forward<T2>(t2)),它为valid。但#2会扩展为std::forward<T1, T2>(t1, t2)),然而它们是ill-formed,因为std::forward只能拥有一个template parameter和一个function parameter。

所以,#1总是valid,但#2只在参数包只有一个元素时才是valid(况且,当只有一个template parameter时,为何还要使用variadic template呢)。

alignas

alignas可用于变量(variable)或非位域类数据成员(non-bitfield class data member)的声明,或用于classstructunionenum的声明或定义。它不能用于函数参数或catch与句的异常参数。

The object or the type declared by such a declaration will have its alignment requirement equal to the strictest (largest) non-zero expression of all alignas specifiers used in the declaration, unless it would weaken the natural alignment of the type.

例子:

// every object of type sse_t will be aligned to 16-byte boundary
struct alignas(16) sse_t {
    float sse_data[4];
};

// the array "cacheline" will be aligned to 128-byte boundary
alignas(128) char cacheline[128];

int main() {
    sse_t i;
    std::cout <<  sizeof(i) <<std::endl;
}
///    16

alignof VS alignas

alignof is an operator that will tell you the byte-multiple for an object’s memory location.
alignas will change the alignment to something perhaps more coarse, but never finer.

假设有一个32位的计算机,现在希望有:alignof(array) ==4。注意,如果数组正好在奇数边界(odd boundary)上,则下列语句可能会导致程序为ill-formed:

double * pd = static_cast<double *>(array);

如果知道如何对齐对象,就可以在声明和定义数组的时候就是用alignment specifier。

alignas(double) char array[sizeof(double)];

扩展:Memory alignment : how to use alignof / alignas?

variadic macro

  1. Standard Predefined Macros
  2. Variadic Macros

利用using代替typedef声明function pointer

利用typedef进行声明:

typedef void (*pointer)();

利用using进行声明:

using pointer = void (*)();

或者:

void f() {}

using FunctionPtr = std::add_pointer<void()>::type;
FunctionPtr ptr = f;

根据元素类型获取tuple中的元素

template <typename T, std::size_t N, typename...Args>
struct index_of;

template <typename T, std::size_t N, typename...Args>
struct index_of<T, N, T, Args...> {
    enum { value = N };
};

template <typename T, std::size_t N, typename F, typename...Args>
struct index_of<T, N, F, Args...> {
    enum {
        value = index_of<T, N+1, Args...>::value
    };
};

template <typename T, std::size_t N>
struct index_of<T, N> {
    enum { value=-1 };
    static_assert(value!=-1, "type is non-existing");
};

template <typename T, typename...Args>
T get_element_by_name(const std::tuple<Args...>& t) {
    return std::get<index_of<T, 0, Args...>::value>(t);
}

int main() {
    std::tuple<char,int,double> t 
        = std::make_tuple('a',1,1.111);
    std::cout<< get_element_by_name<double>(t) <<std::endl;
}

enum换成const int也可以。现在将上述代码完全改为C++ 11的方式,使用std::integral_constant

template <typename T, std::size_t N, typename...Args>
struct index_of;

template <typename T, std::size_t N, typename...Args>
struct index_of<T, N, T, Args...> : std::integral_constant<int, N> {};

template <typename T, std::size_t N, typename F, typename...Args>
struct index_of<T, N, F, Args...> : std::integral_constant<int, index_of<T, N+1, Args...>::value> {};

template <typename T, std::size_t N>
struct index_of<T, N> : std::integral_constant<int, -1> {};

template <typename T, typename...Args>
T get_element_by_name(const std::tuple<Args...>& t) {
    return std::get<index_of<T, 0, Args...>::value>(t);
}

int main() {
    std::tuple<char,int,double> t 
        = std::make_tuple('a',1,1.111);
    std::cout<< get_element_by_name<double>(t) <<std::endl;
}

问题一:

#include <iostream>
auto main = []() ->int { return 0; }
#include <iostream>
int func() { return 0; }
int (*main)() = func;

上述代码无法运行的原因(标准规定):

The function main shall not be used within a program. The linkage of main is implementation-defined. A program that defines main as deleted or that declares main to be inline, static, or constexpr is ill-formed. The main function shall not be declared with a linkage-specification. A program that declares a variable main at global scope or that declares the name main with C language linkage (in any namespace) is ill-formed. The name main is not otherwise reserved. [ Example: Member functions, classes, and enumerations can be called main, as can entities in other namespaces. —end example ]

问题二:

template <typename T>
using remove_reference=T;

此时,想要特化一个T&的版本:

template <typename T>
using remove_reference<T&>=T;

上述的语法不正确。

Because an alias-declaration cannot declare a template-id, it is not possible to partially or explicitly specialize an alias template.因为别名声明不能声明模板 id ,故不可能部分或显式特化一个别名模版。

即:别名模板不能显示特化或者偏特化

所以正确的写法为:

template <typename T>
struct remove_reference { using type = T; }

template <typename T>
struct remove_reference<T&> { using type = T; }

template <typename T>
struct remove_reference<T&&> { using type = T; }

template <typename T>
using remove_reference_t = typename remove_reference<T>::type;