8

因此被问到C++ 中的默认参数

假设我有这样的功能:void f(int p1=1, int p2=2, int p3=3, int p4=4);

我想只使用一些参数来调用它——其余的将是默认值。

像这样的东西会起作用:

template<bool P1=true, bool P2=true, bool P3=true, bool P4=true>
void f(int p1=1, int p2=2, int p3=3, int p4=4);
// specialize:
template<>
void f<false, true, false, false>(int p1) {
  f(1, p1);
}
template<>
void f<false, true, true, false>(int p1, int p2) {
  f(1, p1, p2);
}
// ... and so on. 
// Would need a specialization for each combination of arguments
// which is very tedious and error-prone

// Use:
f<false, true, false, false>(5); // passes 5 as p2 argument

但是它需要太多的代码才能实用。

有一个更好的方法吗?

4

4 回答 4

11

使用命名参数习语(→ FAQ 链接)。

Boost.Parameters 库(→链接)也可以解决此任务,但代价是代码冗长且清晰度大大降低。它在处理构造函数方面也有缺陷。当然,它需要安装 Boost 库。

于 2011-11-18T07:15:55.207 回答
7

看看Boost.Parameter库。

它在 C++ 中实现命名参数。例子:

#include <boost/parameter/name.hpp>
#include <boost/parameter/preprocessor.hpp>
#include <iostream>

//Define
BOOST_PARAMETER_NAME(p1)    
BOOST_PARAMETER_NAME(p2)
BOOST_PARAMETER_NAME(p3)
BOOST_PARAMETER_NAME(p4)

BOOST_PARAMETER_FUNCTION(
                         (void),
                         f,
                         tag,
                         (optional            
                          (p1, *, 1)
                          (p2, *, 2)
                          (p3, *, 3)
                          (p4, *, 4)))
{
    std::cout << "p1: " << p1 
            << ", p2: " << p2
            << ", p3: " << p3
            << ", p4: " << p4 << "\n";
}
//Use
int main()
{
    //Prints "p1: 1, p2: 5, p3: 3, p4: 4"
    f(_p2=5);
}
于 2011-11-18T06:13:56.257 回答
4

尽管 Boost.Parameters 很有趣,但它(不幸地)遇到了许多问题,其中包括占位符冲突(并且必须调试古怪的预处理器/模板错误):

BOOST_PARAMETER_NAME(p1)

将创建_p1您稍后使用的占位符。如果您有两个不同的标头声明同一个占位符,则会发生冲突。不好玩。

有一个更简单(概念上和实践上)的答案,基于BuilderPattern 有点是Named Parameters Idiom

而不是指定这样的功能:

void f(int a, int b, int c = 10, int d = 20);

您指定一个结构,您将在其上覆盖operator()

  • 构造函数用于请求强制参数(不是严格在命名参数成语中,但没有人说你必须盲目遵循它),并为可选参数设置默认值
  • 每个可选参数都有一个设置器

通常,它与Chaining结合使用,其中包括使 setter 返回对当前对象的引用,以便可以将调用链接在一行上。

class f {
public:
  // Take mandatory arguments, set default values
  f(int a, int b): _a(a), _b(b), _c(10), _d(20) {}

  // Define setters for optional arguments
  // Remember the Chaining idiom
  f& c(int v) { _c = v; return *this; }
  f& d(int v) { _d = v; return *this; }

  // Finally define the invocation function
  void operator()() const;

private:
  int _a;
  int _b;
  int _c;
  int _d;
}; // class f

调用是:

f(/*a=*/1, /*b=*/2).c(3)(); // the last () being to actually invoke the function

我见过一个变体将强制参数作为参数放入operator(),这避免了将参数保留为属性,但语法有点奇怪:

f().c(3)(/*a=*/1, /*b=*/2);

一旦编译器内联了所有构造函数和 setter 调用(这就是为什么在这里定义它们,而operator()没有定义),与“常规”函数调用相比,它应该会产生类似高效的代码。

于 2011-11-18T07:39:05.013 回答
2

这不是一个真正的答案,但是...

在David Abrahams 和 Aleksey Gurtovoy 的C++ Template Metaprogramming(2004 年出版!)中,作者谈到了这一点:

在编写本书时,我们重新考虑了用于命名函数参数支持的接口。通过一些实验,我们发现可以通过使用带有重载赋值运算符的关键字对象来提供理想的语法:

f(slew = .799, name = "z");

他们接着说:

我们不打算在这里讨论这个命名参数库的实现细节;它很简单,我们建议您尝试自己将其作为练习来实现。

这是在模板元编程和 Boost::MPL 的上下文中。我不太确定他们的“直截了​​当”的实现如何使用默认参数,但我认为它是透明的。

于 2011-11-18T07:23:28.723 回答