Specifying default parameter when calling C++ function
Asked Answered
T

6

27

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.
Trela answered 6/8, 2016 at 18:10 Comment(3)
You may look at named parameters. There are several tricks to have this feature in C++ as BOOST_PARAMETER_FUNCTION, and then specify which parameter you give.Elwina
If it seems like you need to do this, you may have a design flaw. I'd suggest you re-evaluate it.Tania
@RobK This was just a question of curiosity.Trela
E
15

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);
Elwina answered 6/8, 2016 at 23:0 Comment(1)
This is a good optimal solution. I actually like this and may use it. +1Trela
C
16

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.

Colorist answered 6/8, 2016 at 18:16 Comment(2)
I already read your link, but I already know what you say right now. Thank you for your input though, and if a better answer doesn't show up, I will mark this as correct.Trela
ok. if you believe what it says, then your question is answered. you need to structure your function to enable the behaviour you want. For example, if user inputs some invalid value, perhaps -1, then have it use a default.Colorist
E
15

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);
Elwina answered 6/8, 2016 at 23:0 Comment(1)
This is a good optimal solution. I actually like this and may use it. +1Trela
T
10

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.

Tymothy answered 6/8, 2016 at 18:26 Comment(0)
D
2

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)
Dither answered 28/9, 2016 at 16:51 Comment(0)
H
1

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 https://mcmap.net/q/382899/-how-do-i-find-out-if-a-tuple-contains-a-type
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

Humour answered 6/8, 2016 at 19:21 Comment(5)
Thank you for your answer, but variadic templates.... +1, anyway, and thanks for the linkTrela
@ArnavBorborah what's wrong with variadic templates?Humour
Aren't ellipsis unsafe?Trela
@ArnavBorborah unsafe in which way? btw: a variadic template is not the same as a C ellipsisHumour
Oh ok, I was confusing them, Another stack overflow answer, mentioned c - ellipsis to be dangerous.Trela
B
1

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);
}
Blemish answered 3/8, 2022 at 9:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.