默认实参
默认实参在C++编程实践中非常常见,但其中也有一些有趣的知识点与扩展,本文对默认实参做简单总结;
函数默认实参
默认实参用来取代函数调用中缺失的尾部实参 :
void Process(int x = 3, int y = 4){}
- 拥有默认实参的形参之后的形参必须在同一作用域内有声明提供了默认参数,否则编译失败:
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){} // 编译失败,不在同一作用域
}
- 形参包展开情况:
template
void Process(int f = 0, T ...) {}
- 省略号不是形参,所以它可以跟在带有默认实参的形参之后
void Process(int f = 0, ...) {}
省略号在函数重载决议时拥有最低优先级,可以用于配合模板实参的约束,例如可以用于实现IsConstructible,如下所示:
template
class IsConstructible {
private:
template>
static std::true_type Test(void*);template
>
static std::false_type Test(…);
public:
static constexpr bool value = std::is_same_v<:true_type decltype>(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 // 编译OK
void Process(){}
template // 编译OK
void Process(){}
上述代码中模板实参并不要求是尾部实参,这点与函数实参有区别;
类模板默认模板实参
- 对于类模板默认实参和函数默认实参类似,需要具有默认实参之后的形参都具有默认参,可以与类模板声明中的默认实参进行组合:
template
class Test;
template
class Test {
};
但是不能对模板参数进行重复的默认实参设置;
- 函数模板本身不支持偏特化,但类模板支持,而类模板偏特化中不支持默认实参:
template
class Test {
};
template
class Test{
};
// 编译错误:default template arguments may not be used in partial specializations
- 对于类模板类外定义的函数不支持默认实参:
template
class Test {
void Process();
};
template // 编译失败
void Test::Process()
{
}
- 类内友元函数定义支持默认实参
class Test {
template
friend void Process(Test *)
{}
// 类内声明,类外定义,则不支持默认实参,编译错误
// template
// friend void Process(Test *);
private:
int m_val;
};
- 可变参模板不支持默认模板实参
默认实参扩展
经过上述的简单总结后,可以发现对于函数模板、类模板默认模板实参确定后,用户需要顺序指定模板实参:
template
class Test {
};
用户想要修改第三个默认实参,则需要:
Test
如果模板参数更多的情况下则需要将前n-1个模板实参指定,直到真正想要修改的第n个模板参数,因此希望实现如下效果,指定模板形参T2修改为UserType,而其他仍然保持Default参数;
Test>
本文介绍两种实现方式,使用上稍有不同。
借助std::tuple实现
既然是类型的转换,那可以通过std::tuple来实现,笔者实现代码如下:
struct DefaultType {
static void Process()
{
DBG_LOG("In Default");
}
};
template
struct IsSameVal : std::false_type {};
template
struct IsSameVal: std::true_type {};
template
struct GetPlace {
using Type = DefaultType; // 可扩展每个index有不同的Default
};
template
struct GetPlace {
using Type = std::conditional_t::value, typename Place::Type, typename GetPlace::Type>;
};
template
struct ExpandImpl;
template
struct ExpandImpl<:tuple>, std::tuple, curNum, maxArgsNum> {
using Type = typename ExpandImpl<:tuple typename getplace placeargs...>::Type>, std::tuple, curNum + 1, maxArgsNum>::Type;
};
template
struct ExpandImpl<:tuple>, std::tuple, maxArgsNum, maxArgsNum> {
using Type = std::tuple;
};
#define PLACE(Index)
template
struct Place ## Index {
using Type = T;
static constexpr size_t index = Index;
}
#define MAX_ARGS_NUM (4)
template
using Expand = typename ExpandImpl<:tuple>, std::tuple, 0, MAX_ARGS_NUM>::Type;
模板实参用户通过Expand
PLACE(0);
PLACE(1);
PLACE(2);
PLACE(3);
template
struct Test;
template
struct Test<:tuple 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> t{};
t.Process();
}
{
Test, Place0>> t{};
t.Process();
}
{
Test, Place0>> 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, Place0>>,将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, Place0>>,将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
class Policy1_is : virtual public DefaultPolicies {
public:
using P1 = Policy; // overriding type alias
};
template
class Policy2_is : virtual public DefaultPolicies {
public:
using P2 = Policy; // overriding type alias
};
template
class Policy3_is : virtual public DefaultPolicies {
public:
using P3 = Policy; // overriding type alias
};
template
class Policy4_is : virtual public DefaultPolicies {
public:
using P4 = Policy; // overriding type alias
};
// PolicySelector creates A,B,C,D as base classes
// Discriminator allows having even the same base class more than once
template
class Discriminator : public Base {
};
template
class PolicySelector :
public Discriminator,
public Discriminator,
public Discriminator,
public Discriminator {
};
template
class BreadSlicer {
// use Policies::P1, Policies::P2, … to refer to the various policies
using Policies = PolicySelector;
public:
void print()
{
Policies::P3::doPrint();
}
};
class Custom {
public:
static void doPrint()
{
std::cout obj1;
obj1.print(); // Default实现
BreadSlicer> 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】路漫漫其修远兮,吾将上下而求索
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.e1idc.net