【问题标题】:How can I cleanly specify which arguments I am passing and which remain default?我怎样才能清楚地指定我传递哪些参数以及哪些保持默认?
【发布时间】:2011-12-31 23:30:07
【问题描述】:

问这个问题:Default argument in 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

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

有没有更好的方法来做到这一点?

【问题讨论】:

  • 您到底想要达到什么目的?我觉得无论您尝试做什么,都可以以更简单的方式完成。上面的代码看起来像是一场维护噩梦的开始。
  • @AndersK。一种指定我想使用哪些参数的方法。我同意不应该这样做,但我仍然很好奇是否可能。
  • 你真的需要它吗?您将不得不考虑(N!)个参数放置的变体。 4! = 24 个定义。传递所有参数相当更清晰
  • @Andrey Atapin -- 这不是 n!,只是 2^n,差别很大。
  • 顺便说一句。无论这个特定代码的原因是什么,都会出现类似的情况。在这种情况下,我编写 C++ 代码生成工具来处理它们,有些人使用 m4 宏处理器,有些人使用 VS 宏。我个人更喜欢代码生成而不是宏。

标签: c++ c++11 overloading default-arguments


【解决方案1】:

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

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

【讨论】:

  • 命名参数习语仅在语法上与一堆设置函数不同。后者恕我直言更自然,因为链式函数称气味不好。两种方法都有相同的问题,都是创建 init 类型的函数来代替 ctor 初始化。
  • 请记住,如果您将参数用于模板代码,参数会产生一些您将在编译器中看到的最糟糕的错误消息。
【解决方案2】:

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

【讨论】:

  • 我非常喜欢这个答案,虽然它有点矫枉过正。
  • Boost.Parameters 对于这项任务来说有点矫枉过正。
  • @Pubby: 可能是,但深入研究生成的代码(gcc 上的-E)并看看他们如何通过元编程实现这一点很有趣。这是让您大开眼界的模板之一。
  • @Matthieu:您会发现元编程位微不足道,但极其乏味。并且它并不能全部通过标准语言元编程来解决,而且对于 C++98,也可以通过预处理器的编译器特定的怪癖来解决。我认为,这是 C++ 历史上最伟大的 hack 之一,但除非有迫切需要,否则不会使用:更合理的方法,例如 NPI,可以更简单有效地处理最常见的情况。
  • @AlfP.Steinbach:我同意预处理器黑客,这就是为什么我只(希望)谈论模板并建议查看预处理器输出。我对解决方案的巧妙程度并不感到惊讶,更多的是想法本身。这不是您在查看模板时直观地想到的东西,但它使用起来非常优雅(但是,正如您所说,需要一些样板代码来设置)。
【解决方案3】:

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

BOOST_PARAMETER_NAME(p1)

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

基于Builder 模式,有一个更简单(概念上和实践上)的答案,有点类似于命名参数习语。

而不是指定这样的函数:

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

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

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

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

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() 未定义),与“常规”函数调用相比,它应该会产生类似高效的代码。

【讨论】:

    【解决方案4】:

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

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

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

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

    他们接着说:

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

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

    【讨论】:

    • 这实际上是 Boost.Parameters 背后的魔法核心。我也成功地将它用于 DSL,它确实创建了简洁的界面。
    • @MatthieuM。所以我所要做的就是阅读 Boost.Parameters 标题来学习这个(黑色?)魔法?在此期间,我想我会阅读所有 Boost 资源。然后我可以在 70 岁时成为一名优秀的 C++ 程序员。:)
    • 坦率地说,我不能说我想使用 boost.parameters,这是一件很奇怪的事情,但在实际使用中,治愈比疾病更糟糕。我会使用代码生成或(如果性能不重要)简单的几行解析器并向函数提供字符串:f("slew = .799, name = \"z\"");
    猜你喜欢
    • 2017-06-05
    • 2015-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多