如何干净利落地指定哪些参数是我传递的,哪些仍是默认参数?

问题描述 投票:8回答:4

因为这个问题而被问到: 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

但它需要太多的代码,不实用。

有没有更好的方法?

c++ c++11 overloading default-arguments
4个回答
11
投票

使用 命名参数成语 (→ 常见问题链接).

Boost.参数库 (→ 联系)也可以解决这个任务,但付出的代价是代码啰嗦,清晰度大大降低。它在处理构造函数方面也有不足。当然,它需要安装Boost库。


7
投票

请看一下 Boost.Parameter 库。

它用C++实现了命名的paramaters。例如:Boost.Parameters。

#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);
}

4
投票

虽然Boost.Parameters很有趣, 但它有很多问题(不幸的是), 其中包括占位符碰撞(和不得不调试古怪的预处理器模板错误):

BOOST_PARAMETER_NAME(p1)

将创建 _p1 占位符,然后你再使用。如果你有两个不同的头文件声明同一个占位符,你就会有冲突。这可不好玩。

有一个更简单的答案(无论从概念上还是从实践上),它基于 Builder 模式有点是 命名参数成语.

而不是指定这样一个函数。

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

你指定了一个结构,在这个结构上你将覆盖 operator():

  • 构造函数是用来询问必选参数的(严格来说不是Named Parameters的成语,但没有人说你必须盲目遵循),而对可选参数则设置默认值
  • 每个可选参数都有一个设定器

一般来说,它与 链式 它包括使设置者返回对当前对象的引用,这样调用就可以在一行上链起来。

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);

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


2
投票

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

C++模板元编程 由David Abrahams和Aleksey Gurtovoy撰写(2004年出版!),作者谈到了这个问题。

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

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

他们继续说:

我们不打算在这里讨论这个命名参数库的实现细节,它非常直接,我们建议你自己尝试实现它,作为一种练习。

这是在模板元编程和Boost::MPL的背景下说的。 我不太清楚他们的 "直截了当 "的实现会如何与默认参数相配合,但我认为这将是透明的。

© www.soinside.com 2019 - 2024. All rights reserved.