调用C++函数时指定默认参数

26

假设我的代码如下:

void f(int a = 0, int b = 0, int c = 0)
{
    //...Some Code...
}

从我的代码中可以明显看出,参数abc的默认参数值为0。现在请看下面的主函数:

int main()
{
   //Here are 4 ways of calling the above function:
   int a = 2;
   int b = 3;
   int c = -1;

   f(a, b, c);
   f(a, b);
   f(a); 
   f();
   //note the above parameters could be changed for the other variables
   //as well.
}

现在我知道不能跳过参数,使用默认值,因为默认值将作为该位置的参数进行计算。我的意思是,我不能只调用f(a,c),因为c将被计算为b,这不是我想要的,尤其是如果c是错误的类型。在C++中,有没有一种方法可以让调用函数指定在任何给定位置使用函数的默认参数值,而不仅仅是从最后一个参数向后省略?是否有任何保留关键字可实现此操作,或者至少有一种解决方法?我可以举个例子:

f(a, def, c) //Where def would mean default.

你可以查看命名参数。在C++中有一些技巧可以实现这个功能,比如BOOST_PARAMETER_FUNCTION,然后指定你要给出的参数。 - Jarod42
如果你觉得需要这样做,那么可能存在设计缺陷。我建议你重新评估一下。 - Rob K
1
@RobK 这只是一个好奇的问题。 - Arnav Borborah
6个回答

16

对于这种情况,没有保留字可用,f(a,,c)也无效。您可以省略最右边的多个可选参数,就像您展示的那样,但不能像那样省略中间的参数。

http://www.learncpp.com/cpp-tutorial/77-default-parameters/

引用上述链接的原话:

多个默认参数

函数可以有多个默认参数:

void printValues(int x=10, int y=20, int z=30)
{
    std::cout << "Values: " << x << " " << y << " " << z << '\n';
}

给定以下函数调用:

printValues(1, 2, 3);
printValues(1, 2);
printValues(1);
printValues();
以下输出结果产生:
Values: 1 2 3
Values: 1 2 30
Values: 1 20 30
Values: 10 20 30

请注意,如果没有为x和y提供值,则无法为z提供用户定义的值。这是因为C++不支持函数调用语法,例如printValues(,,3)。这有两个重要后果:

1)所有默认参数必须位于最右侧的参数位置。以下是不允许的:

void printValue(int x=10, int y); // not allowed

2) 如果存在多个默认参数,则最左边的默认参数应该是用户最有可能显式设置的参数。


4
我已经阅读了你的链接,不过我现在已经知道你所说的内容。谢谢你的回答,如果没有更好的答案出现,我会将其标记为正确答案。 - Arnav Borborah
如果您相信它所说的,那么您的问题已经得到解答。您需要构建函数以实现您想要的行为。例如,如果用户输入了一些无效值,比如-1,则让它使用默认值。 - Alejandro

15
作为一种解决方法,您可以(滥用)使用boost::optional(直到c++17的std::optional):
void f(boost::optional<int> oa = boost::none,
       boost::optional<int> ob = boost::none,
       boost::optional<int> oc = boost::none)
{
    int a = oa.value_or(0); // Real default value go here
    int b = ob.value_or(0); // Real default value go here
    int c = oc.value_or(0); // Real default value go here

    //...Some Code...
}

然后调用它

f(a, boost::none, c);

1
这是一个很好的最优解决方案。我实际上很喜欢它,可能会使用它。+1 - Arnav Borborah

10
< p >虽然不完全符合您的要求,但您可以使用std::bind()来为参数设置一个值。

就像这样

#include <functional>

void f(int a = 0, int b = 0, int c = 0)
{
    //...Some Code...
}

int main()
{
   // Here are 4 ways of calling the above function:
   int a = 2;
   int b = 3;
   int c = -1;

   f(a, b, c);
   f(a, b);
   f(a); 
   f();
   // note the above parameters could be changed 
   // for the other variables as well.

   using namespace std::placeholders;  // for _1, _2

   auto f1 = std::bind(f, _1, 0, _2);

   f1(a, c); // call f(a, 0, c);

   return 0;
}

使用std::bind(),你可以固定与默认参数值不同的值或没有默认值的参数的值。

请注意,std::bind()只在C++11及以上版本中可用。


2

您已经有一个被接受的答案,但这里提供另一种解决方法(我相信它比其他提出的解决方法更具优势):

您可以强类型化参数:

struct A { int value = 0; };
struct B { int value = 2; };
struct C { int value = 4; };

void f(A a = {}, B b = {}, C c = {}) {}
void f(A a, C c) {}

int main()
{
    auto a = 0;
    auto b = -5;
    auto c = 1;

    f(a, b, c);
    f(a, C{2});
    f({}, {}, 3);
}

优点:

  • 这种方式简单易于维护(每个参数只需要一行代码)。
  • 提供了一个自然的点来进一步限制API的范围(例如,“如果B的值为负数,则抛出异常”)。
  • 不会妨碍其他功能的使用(可与默认构造函数、智能提示/自动完成等一样使用)。
  • 它是自说明的。
  • 速度和本地版本一样快。

缺点:

  • 增加了名称污染(最好将所有内容放在命名空间中)。
  • 虽然简单,但仍需要更多的代码来维护(而不只是直接定义函数)。
  • 可能会引起一些困惑(可以考虑添加注释以解释为什么需要强类型)。

1
如果函数的所有参数类型都是不同的,您可以找出哪些参数被传递了,哪些没有被传递,并为后者选择默认值。
为了满足不同类型的要求,您可以将参数封装并将其传递给可变函数模板。然后,参数的顺序就不再重要了:
#include <tuple>
#include <iostream>
#include <type_traits>

// -----
// from https://dev59.com/ql8e5IYBdhLWcg3wNYUn#25958302
template <typename T, typename Tuple>
struct has_type;

template <typename T>
struct has_type<T, std::tuple<>> : std::false_type {};

template <typename T, typename U, typename... Ts>
struct has_type<T, std::tuple<U, Ts...>> : has_type<T, std::tuple<Ts...>> {};

template <typename T, typename... Ts>
struct has_type<T, std::tuple<T, Ts...>> : std::true_type {};

template <typename T, typename Tuple>
using tuple_contains_type = typename has_type<T, Tuple>::type;
//------


template <typename Tag, typename T, T def>
struct Value{
    Value() : v(def){}
    Value(T v) : v(v){}
    T v; 
};

using A = Value<struct A_, int, 1>;
using B = Value<struct B_, int, 2>;
using C = Value<struct C_, int, 3>;


template <typename T, typename Tuple>
std::enable_if_t<tuple_contains_type<T, Tuple>::value, T> getValueOrDefaultImpl(Tuple t)
{
    return std::get<T>(t);
}

template <typename T, typename Tuple>
std::enable_if_t<!tuple_contains_type<T, Tuple>::value, T> getValueOrDefaultImpl(Tuple)
{
    return T{};
}

template <typename InputTuple, typename... Params>
auto getValueOrDefault(std::tuple<Params...>, InputTuple t)
{
    return std::make_tuple(getValueOrDefaultImpl<Params>(t)...);
}

template <typename... Params, typename ArgTuple>
auto getParams(ArgTuple argTuple) 
{
    using ParamTuple = std::tuple<Params...>;
    ParamTuple allValues = getValueOrDefault(ParamTuple{}, argTuple);
    return allValues;
}

template <typename... Args>
void f(Args ... args)
{
    auto allParams = getParams<A,B,C>(std::make_tuple(args...));
    std::cout << "a = " << std::get<A>(allParams).v << " b = " << std::get<B>(allParams).v << " c = " << std::get<C>(allParams).v << std::endl;
}

int main()
{
   A a{10};
   B b{100};
   C c{1000};

   f(a, b, c);
   f(b, c, a);
   f(a, b);
   f(a); 
   f();
}

output

a = 10 b = 100 c = 1000
a = 10 b = 100 c = 1000
a = 10 b = 100 c = 3
a = 10 b = 2 c = 3
a = 1 b = 2 c = 3

实时例子


1
谢谢您的回答,但是可变参数模板...无论如何加1,并感谢提供链接。 - Arnav Borborah
@ArnavBorborah,可变参数模板有什么问题吗? - m.s.
1
省略号不安全吗? - Arnav Borborah
@ArnavBorborah 不安全的方式是什么?顺便说一句:可变参数模板与 C 语言中的省略号不同。 - m.s.
1
哦,好的,我把它们搞混了。另一个堆栈溢出的答案提到,c-省略号是危险的。 - Arnav Borborah

1
我将仅使用静态函数来定义可以更改的默认值:
class defValsExample 
{
public: 
    defValsExample() {
    }

    static int f1def_a() { return 1; }
    static int f1def_b() { return 2; }

    int f1(int a = f1def_a(), int b = f1def_b()) {
        return a+b;
    }
};

int main()
{
    defValsExample t; 

    int c = t.f1(t.f1def_a(),4);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接