首页 > 其他分享 >Default Arguments总结

Default Arguments总结

时间:2023-04-15 21:32:08浏览次数:45  
标签:总结 Default void 默认 Process int Arguments template 实参

默认实参

默认实参在C++编程实践中非常常见,但其中也有一些有趣的知识点与扩展,本文对默认实参做简单总结;

函数默认实参

默认实参用来取代函数调用中缺失的尾部实参 :

void Process(int x = 3, int y = 4){}
  1. 拥有默认实参的形参之后的形参必须在同一作用域内有声明提供了默认参数,否则编译失败:
void Process(int x = 3, int y){} // 编译失败
void Process(int x, int y = 4);
void Process(int x = 3, int y){} // 编译成功
void Process(int x, int y = 4);
namespace Test {
void Process(int x = 3, int y){} // 编译失败,不在同一作用域
}
  1. 形参包展开情况:
template<typename ...T>
void Process(int f = 0, T ...) {}
  1. 省略号不是形参,所以它可以跟在带有默认实参的形参之后
void Process(int f = 0, ...) {}

省略号在函数重载决议时拥有最低优先级,可以用于配合模板实参的约束,例如可以用于实现IsConstructible,如下所示:

template<typename U>
class IsConstructible {
private:
template<typename T, typename = std::void_t<decltype(T())>>
static std::true_type Test(void*);

template<typename T, typename = std::void_t<>>
static std::false_type Test(...);
public:
static constexpr bool value = std::is_same_v<std::true_type, decltype(Test<U>(nullptr))>;
};

成员函数默认实参

对于非模板类的成员函数,类外的定义的默认实参与类体内的声明所提供的默认实参相组合

class Test {
public:
    void Process(int a, int b = 4);
};
void Test::Process(int a = 3, int b) 
{
}

对于继承中的成员函数默认参数需要注意,看一下代码示例:

struct Base {
    virtual void Process(int a = 3, int b = 4)
    {
    }
};

struct Derive: Base {
    void Process(int a = 5, int b = 6)
    {
        DBG_LOG("a: %d b: %d", a, b);
    }
};

int main()
{
    Derive d {};
    Base &b = d;
    b.Process();    
}
输出:2023-4-15:10:45:25.148623[DBG  ][Process:19] a: 3 b: 4

可以发现派生类的默认实参并没有覆盖基类,如果直接想当然在实践中就会落下bug,改写下调用:

int main()
{
    Derive d {};
    d.Process();    
}
输出:2023-4-15:10:49:17.761961[DBG  ][Process:19] a: 5 b: 6

直接用派生类调用Process函数,则发现此时默认参数变成了派生类的默认实参,这其中的原因为默认实参是跟对象的静态类型绑定的;因此在实际编码中往往会规范派生类函数不设置默认实参,以免造成不必要的困惑和bug;

此外还有一些约束,可作为了解【1】:

// 默认实参中不能使用局部变量;
// 默认实参中不能使用 [`this`](this.html) 指针: 
class A
{
    void f(A* p = this) {} // 错误:不能使用 this
};
// 默认实参中不能使用非静态的类成员
class Test {
public:
    void Process(int a = m_a) {} // 编译OK
    static inline int m_a = 0;
};
class Test {
public:
    int operator[](int i = 0) { return 0; } // 编译失败
    int operator()(int i = 0) { return 0; } // 编译成功
};

函数模板默认模板实参

template<typename T1, typename T2 = int> // 编译OK
void Process(){}
template<typename T1 = int, typename T2>  // 编译OK
void Process(){}

上述代码中模板实参并不要求是尾部实参,这点与函数实参有区别;

类模板默认模板实参

  1. 对于类模板默认实参和函数默认实参类似,需要具有默认实参之后的形参都具有默认参,可以与类模板声明中的默认实参进行组合:
template<typename T1, typename T2, typename T3 = int>
class Test;

template<typename T1, typename T2 = int, typename T3>
class Test {
};

但是不能对模板参数进行重复的默认实参设置;

  1. 函数模板本身不支持偏特化,但类模板支持,而类模板偏特化中不支持默认实参:
template<typename T1>
class Test {
};

template<typename T1 = int>
class Test<T1*>{
};
// 编译错误:default template arguments may not be used in partial specializations
  1. 对于类模板类外定义的函数不支持默认实参:
template<typename T1>
class Test {
    void Process();
};

template<typename T = int> // 编译失败
void Test<T>::Process()
{
}
  1. 类内友元函数定义支持默认实参
class Test {
    template<typename T = int>
    friend void Process(Test *)
    {}
    // 类内声明,类外定义,则不支持默认实参,编译错误
    // template<typename T = int>
    // friend void Process(Test *); 
private:
    int m_val;
};
  1. 可变参模板不支持默认模板实参

默认实参扩展

经过上述的简单总结后,可以发现对于函数模板、类模板默认模板实参确定后,用户需要顺序指定模板实参:

template<typename T0 = Default, typename T1 = Default, typename T2 = Default, typename T3 = Default>
class Test {
};

用户想要修改第三个默认实参,则需要:

Test<Default, Default, UserType>

如果模板参数更多的情况下则需要将前n-1个模板实参指定,直到真正想要修改的第n个模板参数,因此希望实现如下效果,指定模板形参T2修改为UserType,而其他仍然保持Default参数;

Test<Place3<UserType>>

本文介绍两种实现方式,使用上稍有不同。

借助std::tuple实现

既然是类型的转换,那可以通过std::tuple来实现,笔者实现代码如下:

struct DefaultType {
    static void Process()
    {
        DBG_LOG("In Default");
    }
};

template<size_t v1, size_t v2>
struct IsSameVal : std::false_type {};
template<size_t v>
struct IsSameVal<v, v>: std::true_type {};

template<size_t index, typename...PlaceArgs>
struct GetPlace {
    using Type = DefaultType; // 可扩展每个index有不同的Default
};
template<size_t index, typename Place, typename...PlaceArgs>
struct GetPlace<index, Place, PlaceArgs...> {
    using Type = std::conditional_t<IsSameVal<index, Place::index>::value, typename Place::Type, typename GetPlace<index, PlaceArgs...>::Type>;
};

template<typename T1, typename T2, size_t curNum, size_t maxArgsNum>
struct ExpandImpl;

template<typename ...Args, typename ...PlaceArgs, size_t curNum, size_t maxArgsNum>
struct ExpandImpl<std::tuple<Args...>, std::tuple<PlaceArgs...>, curNum, maxArgsNum> {
    using Type = typename ExpandImpl<std::tuple<Args..., typename GetPlace<curNum, PlaceArgs...>::Type>, std::tuple<PlaceArgs...>, curNum + 1, maxArgsNum>::Type;
};

template<typename ...Args, typename ...PlaceArgs, size_t maxArgsNum>
struct ExpandImpl<std::tuple<Args...>, std::tuple<PlaceArgs...>, maxArgsNum, maxArgsNum> {
    using Type = std::tuple<Args...>;
};

#define PLACE(Index) \
template<typename T> \
struct Place ## Index { \
    using Type = T; \
    static constexpr size_t index = Index;\
}

#define MAX_ARGS_NUM (4)
template<typename ...Args>
using Expand = typename ExpandImpl<std::tuple<>, std::tuple<Args...>, 0, MAX_ARGS_NUM>::Type;

模板实参用户通过Expand<Place2<Custom>, Place0<Custom>>便可以指定对应位置的模板形参类型,其他未指定的则为默认Default类型;

PLACE(0);
PLACE(1);
PLACE(2);
PLACE(3);

template<typename T>
struct Test;

template<typename T0, typename T1, typename T2, typename T3>
struct Test<std::tuple<T0, T1, T2, T3>> {
    void Process()
    {
        T0::Process();
        T1::Process();
        T2::Process();
        T3::Process();
    }
};

struct Custom {
    static void Process()
    {
        DBG_LOG("In Custom");
    }
};

struct Custom2 {
    static void Process()
    {
        DBG_LOG("In Custom2");
    }
};

int main()
{
    {
        Test<Expand<>> t{};
        t.Process(); 
    }
    {
        Test<Expand<Place2<Custom>, Place0<Custom>>> t{};
        t.Process();
    }
    {
        Test<Expand<Place2<Custom2>, Place0<Custom>>> t{};
        t.Process();
    }
}
// 输出
2023-4-15:17:48:41.305502[DBG  ][Process:13] In Default 
2023-4-15:17:48:41.306385[DBG  ][Process:13] In Default
2023-4-15:17:48:41.306683[DBG  ][Process:13] In Default
2023-4-15:17:48:41.306966[DBG  ][Process:13] In Default
// Test<Expand<Place2<Custom>, Place0<Custom>>>,将T0和T2替换为Custom
2023-4-15:17:48:41.307259[DBG  ][Process:78] In Custom
2023-4-15:17:48:41.307507[DBG  ][Process:13] In Default
2023-4-15:17:48:41.307794[DBG  ][Process:78] In Custom
2023-4-15:17:48:41.308093[DBG  ][Process:13] In Default
// Test<Expand<Place2<Custom>, Place0<Custom>>>,将T0替换Custom,T2替换为Custom2
2023-4-15:17:48:41.308333[DBG  ][Process:78] In Custom
2023-4-15:17:48:41.308555[DBG  ][Process:13] In Default
2023-4-15:17:48:41.308895[DBG  ][Process:85] In Custom2
2023-4-15:17:48:41.309204[DBG  ][Process:13] In Default

Named Template Arguments实现

本实现来自【2】,借助虚继承实现,但相比扩展性个人会选择上一节中的实现;

class DefaultPolicy1 {};
class DefaultPolicy2 {};
class DefaultPolicy3 {
public:
	static void doPrint()
	{
		std::cout << "In DefaultPolicy3" << std::endl;
	}
};
class DefaultPolicy4 {};

// name default policies as P1, P2, P3, P4 
class DefaultPolicies {
public:
	using P1 = DefaultPolicy1;
	using P2 = DefaultPolicy2;
	using P3 = DefaultPolicy3;
	using P4 = DefaultPolicy4;
};

// class to define a use of the default policy values 
// avoids ambiguities if we derive from DefaultPolicies more than once 
class DefaultPolicyArgs : virtual public DefaultPolicies {
};

template<typename Policy> 
class Policy1_is : virtual public DefaultPolicies {
public: 
	using P1 = Policy; // overriding type alias 
};

template<typename Policy> 
class Policy2_is : virtual public DefaultPolicies {
public: 
	using P2 = Policy; // overriding type alias 
};

template<typename Policy> 
class Policy3_is : virtual public DefaultPolicies {
public: 
	using P3 = Policy; // overriding type alias 
};

template<typename Policy> 
class Policy4_is : virtual public DefaultPolicies {
public: 
	using P4 = Policy; // overriding type alias
};


// PolicySelector<A,B,C,D> creates A,B,C,D as base classes 
// Discriminator<> allows having even the same base class more than once 
template<typename Base, int D>
class Discriminator : public Base {
};

template<
	typename Setter1,
	typename Setter2,
	typename Setter3,
	typename Setter4>
class PolicySelector :
	public Discriminator<Setter1, 1>,
	public Discriminator<Setter2, 2>,
	public Discriminator<Setter3, 3>,
	public Discriminator<Setter4, 4> {
};

template<
	typename PolicySetter1 = DefaultPolicyArgs,
	typename PolicySetter2 = DefaultPolicyArgs,
	typename PolicySetter3 = DefaultPolicyArgs,
	typename PolicySetter4 = DefaultPolicyArgs>
class BreadSlicer {
	// use Policies::P1, Policies::P2, … to refer to the various policies 
	using Policies = PolicySelector<PolicySetter1, PolicySetter2, PolicySetter3, PolicySetter4>;

public: 
	void print()
	{ 
		Policies::P3::doPrint(); 
	}
};


class Custom {
public:
	static void doPrint()
	{
		std::cout << "In Custom" << std::endl;
	}
};

int main()
{
	BreadSlicer<> obj1;
	obj1.print();  // Default实现

	BreadSlicer<Policy3_is<Custom>> obj2;
	obj2.print();  // Custom实现
}

参考资料

【1】https://en.cppreference.com/w/cpp/language/default_arguments

【2】C++ Templates

【3】https://zhuanlan.zhihu.com/p/585183393

【3】路漫漫其修远兮,吾将上下而求索

标签:总结,Default,void,默认,Process,int,Arguments,template,实参
From: https://blog.51cto.com/u_13137973/6192464

相关文章

  • scrum项目冲刺_Day4会议总结
    今日团队任务:图片转excel(5天)前端开发(需团队风格统一)调用接口(后端),json数据->excel前后端连接           任烁玚(进行中)            图片转html(8天)前端开发(需团队风格统一)图片转为pdf(存储)pdf转html(调用接口)[html存储到数据库]前后台数据同......
  • 总结与归纳之字符串
    (大的不能在大的坑)前言总论+前置芝士正文字符串哈希KMP算法传统KMP算法Z函数fail树KMP自动机Trie与AC自动机普通Trie01Trie可持久化TrieAC自动机SA相关SA传统SAM广义SAM后缀平衡树ManacherPAM序列自动机最小表示法玄学:Lyndon分解总结......
  • QML 信号与响应方法的总结
    以下内容为本人的著作,如需要转载,请声明原文链接微信公众号「englyf」https://mp.weixin.qq.com/s/8orMKb803oz_G0sSOnDjDw如果面试过程中,面试官想了解你对Qt的理解有多少,少不了会涉及到信号槽这一块,毕竟这是Qt最经典的一项技术。刚开笔,我可能有点狂妄了。信号槽,分为两部分......
  • 总结与归纳之数学
    (巨坑好吧)前言前置的知识正文同余问题大杂烩玄学:Miller-Rabin&Pollard-rho线性代数大杂烩组合数学大杂烩筛法反演问题大杂烩玄学:群论问题大杂烩多项式与生成函数大杂烩玄学:线性规划博弈论问题大杂烩微积分问题小杂烩?计算几何问题大杂烩......
  • JAVA 循环删除list中元素的方法总结
    摘要:介绍List集合实现元素边遍历边删除的方法,例如removeIf和迭代器iterator.remove()等。综述  List集合是我们开发中经常使用到的一种集合形式,有时候会遇到在遍历List集合时需要删除指定的元素。但在根据条件使用for循环或者增强的for循环遍历删除某些元素时却不能随心所欲地......
  • linux网络开发者定位问题常用工具和命令总结
    本文章来自我的微信个人技术公众号---网络技术修炼,公众号中总结普及网络基础知识,包括基础原理、网络方案、开发经验和问题定位案例等,欢迎关注。Linux网络开发者面临的问题往往比较复杂,因此需要使用一些工具和命令来进行定位和解决。在本篇博客中,我将总结一些常用的Linux网络开发......
  • 总结20230414
    今天周五,一周课最多了,但是今天也是很开心的!今天上的是计算机网络、概率论、web应用开发技术、数学建模B。计算机网络讲的是ARP协议以及具体传输时的传输过程,这几节课感觉听得挺不错的,挺轻松的。概率论讲的是二维随机变量分布的函数以及习题,一开始听的有点懵,但是后来悟明白了,边......
  • ajax面试题总结
    转载请注明出处:1.ajax异步和同步的区别Ajax是一种基于JavaScript语言和XMLHttpRequest对象的异步数据传输技术,通过它可以使不用刷新整个页面的情况下,对页面进行部分更新。同步和异步是指客户端发送请求时,主线程是否会阻塞等待服务器的响应返回。同步请求在发送请......
  • pandas库简单用法总结
    简介pandas 是基于NumPy的一种工具,主要用途是做数据分析,对于初学者,比较常用的就是处理csv或者excel文件DataFrame数据结构DataFrame组成DataFrame是Pandas的重要数据结构之一,也是在使用Pandas进行数据分析过程中最常用的结构之一。DataFrame一个表格型的数据结构,既有......
  • 4.14每日总结
    今天做了什么:根据用户消费比例给出消费建议,识别微信截图信息并添加到记账页面,学习了cookie和session的使用遇到了哪些问题:拍照获得的bitmap的data有限制大小,导致识别图片模糊明天打算做什么:攻克拍照获得图片模糊......