简体   繁体   中英

Specifying default parameter when calling C++ function

Suppose I have code like this:

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

As you can evidently see above with my code, the parameters a , b , and c have default parameter values of 0. Now take a look at my main function below:

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.
}

Now I know that I can't just skip a parameter, and let it have the default value, because that value would evaluate as the parameter at that position. What I mean is, that I cannot, say call, f(a,c) , because, c would be evaluated as b , which is what I don't want, especially if c is the wrong type. Is there a way for the calling function to specify in C++, to use whatever default parameter value there is for the function in any given position, without being limited to going backwards from the last parameter to none? Is there any reserved keyword to achieve this, or at least a work-around? An example I can give would be like:

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

There isn't a reserved word for this, and f(a,,c) is not valid either. You can omit a number of rightmost optional parameters, as you show, but not the middle one like that.

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

Quoting directly from the link above:

Multiple default parameters

A function can have multiple default parameters:

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

Given the following function calls:

 printValues(1, 2, 3); printValues(1, 2); printValues(1); printValues();

The following output is produced:

 Values: 1 2 3 Values: 1 2 30 Values: 1 20 30 Values: 10 20 30

Note that it is impossible to supply a user-defined value for z without also supplying a value for x and y. This is because C++ does not support a function call syntax such as printValues(,,3). This has two major consequences:

1) All default parameters must be the rightmost parameters. The following is not allowed:

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

2) If more than one default parameter exists, the leftmost default parameter should be the one most likely to be explicitly set by the user.

As workaround, you may (ab)use boost::optional (until std::optional from c++17):

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...
}

and then call it

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

Not exactly what you asked for, but you can use std::bind() to fix a value for a parameter.

Something like

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

With std::bind() you can fix values different from default parameters' values or values for parameters without default values.

Take into account that std::bind() is available only from C++11.

If all parameters of the function were of distinct types, you could find out which parameters were passed and which were not and choose the default value for the latter.

In order to achieve the distinct type requirement, you can wrap your parameters and pass it to a variadic function template. Then even the order of the argument does not matter anymore:

#include <tuple>
#include <iostream>
#include <type_traits>

// -----
// from http://stackoverflow.com/a/25958302/678093
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

live example

You already have an accepted answer, but here's another workaround (that - I believe - has advantages over the other proposed workarounds):

You can strong-type the arguments:

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

Advantages:

  • it's simple and easy to maintain (one line per argument).
  • provides a natural point for constricting the API further (for example, "throw if B's value is negative").
  • it doesn't get in the way (works with default construction, works with intellisense/auto-complete/whatever as good as any other class)
  • it is self-documenting.
  • it's as fast as the native version.

Disadvantages:

  • increases name pollution (better put all this in a namespace).
  • while simple, it is still more code to maintain (than just defining the function directly).
  • it may raise a few eyebrows (consider adding a comment on why strong-typing is needed)

I will just use static functions to define default values that can change:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM