Default value to a parameter while passing by reference in C++
Asked Answered
P

18

146

Is it possible to give a default value to a parameter of a function while we are passing the parameter by reference. in C++

For example, when I try to declare a function like:

virtual const ULONG Write(ULONG &State = 0, bool sequence = true);

When I do this it gives an error:

error C2440: 'default argument' : cannot convert from 'const int' to 'unsigned long &' A reference that is not to 'const' cannot be bound to a non-lvalue

Pomelo answered 29/6, 2009 at 18:5 Comment(20)
Don't do this. Google style guide (and others) ban non-const pass by reference and they ban default values, both for good reasons. Here you've got the double whammy.Crary
What should the function do? If we know what it has to achieve, we can give you useful alternatives.Quackenbush
@jeffamaphone: why ban default values?Merl
Mercifully, we are not bound by the Google style guide.Bolo
i have a huge code base that uses the function Write. Most of the function calls in the existing code calls Write() without any parameter. So the only option is to make the new parameter with a default value. But the new functionality i need to add is to pass State by reference to Write() which would change the value of state which would be hence visible outside of the function. What's the solution to this?Pomelo
"Don't do this. Google style guide (and others) ban non-const pass by reference" i think style guides are known to contain many subjective parts. This looks like one of them.Quackenbush
WxWidgets style guide says "don't use templates" and they have good reasons <- screw std::vector, i sayQuackenbush
@sony: create a new function, WriteState.Merl
This is what function overloading was for. Though that method is definately a better way.Forelimb
also, what does it return, and what is going to get into the out-argument? you may aswell return a struct with both long values combined, and then remove the reference parameter, which clearly is the evil thing in this part.Quackenbush
@jeffamaphone: Google Style Guide also says not to use streams. Will you suggest avoiding them too?Gorgoneion
I don't think the GSG is right about non-const references, but the advice to avoid default parameters is sound. The code which uses default parameters is usually not easy to read, because callMe(5) may in reality supply a bunch of values to the underlying algorithms which you have no knowledge about when you look at the client code, but which are relevant to you (so encapsulation principle does not apply here).Nymphalid
I have no problem using the default arguments with STL containers and std::string. Would anybody care to convince me otherwise?Scarlatti
This is different. STL has a well-known, well-defined interface. Once you start using default parameters in your in-house code, it creeps all over the place. You get monstrosities like price(a) calling price(a, 0) calling price(a, 0, false) calling price (a, 0, false, null) etc.Nymphalid
I've only seen the price(a) monstrosity in Java-style overloading meant to imitate default parameters.Scarlatti
A hammer is a horrible tool to place a screwdriver, but it is quite usable with nails. The fact that you can misuse a feature should only warn you about possible pitfalls but not prohibit its use.Haircloth
Ooh, Google says do X. So what?Shelby
The restriction in the google style guide, whether it has good reasons or not, has nothing at all to do with this question and the reason that non-lvalues cannot be bound to refs.Acrostic
" the only option is to make the new parameter with a default value" -- um, no, that isn't the only option, certainly not with a ref parameter.Acrostic
@Nymphalid Default parameters are basically syntactic sugar for overloaded functions. Would you ban overloaded functions as well?Colostrum
B
129

You can do it for a const reference, but not for a non-const one. This is because C++ does not allow a temporary (the default value in this case) to be bound to non-const reference.

One way round this would be to use an actual instance as the default:

static int AVAL = 1;

void f( int & x = AVAL ) {
   // stuff
} 

int main() {
     f();       // equivalent to f(AVAL);
}

but this is of very limited practical use.

Bolo answered 29/6, 2009 at 18:8 Comment(11)
if i make it const, will it allow me to pass a different address to the function? or will the address of State be always 0 and so meaningless?Pomelo
If you are using references, you are not passing addresses.Bolo
boost::array to the rescue void f(int &x = boost::array<int,1>()[0]) { .. } :)Quackenbush
if not addresses what's actually getting passed?Pomelo
@Pomelo A reference. It is wriong to think of it as an address. If you want an address, use a pointer.Bolo
so in that case, If i do as below, will that work? void f( int * x = 0 ) { // stuff }Pomelo
Well, that passes a NULL pointer. I think you need to edit your original question to explain better what you are trying to do.Bolo
I found it useful to overload a function which might return multiple values, but the caller might not care for some of them. The caller can simply not specify them instead of having to create a local varHansel
@JohannesSchaub-litb is there a C++17 solution for this?Ploss
I think all he's saying is that internally, a reference is implemented effectively by a pointer.Picaroon
Your work around, making the ref non-const and directing it to an external static var is also the only I've found to work. It is useful because it then allows a var to be passed and altered. If the global static is assigned an "impossible" dummy value, the function can also detect that value and choose not to alter the ref variable in that case.Heiskell
T
45

It has been said in one of the direct comments to your answer already, but just to state it officially. What you want to use is an overload:

virtual const ULONG Write(ULONG &State, bool sequence);
inline const ULONG Write()
{
  ULONG state;
  bool sequence = true;
  Write (state, sequence);
}

Using function overloads also have additional benefits. Firstly you can default any argument you wish:

class A {}; 
class B {}; 
class C {};

void foo (A const &, B const &, C const &);
void foo (B const &, C const &); // A defaulted
void foo (A const &, C const &); // B defaulted
void foo (C const &); // A & B defaulted etc...

It is also possible to redefine default arguments to virtual functions in derived class, which overloading avoids:

class Base {
public:
  virtual void f1 (int i = 0);  // default '0'

  virtual void f2 (int);
  inline void f2 () {
    f2(0);                      // equivalent to default of '0'
  }
};

class Derived : public Base{
public:
  virtual void f1 (int i = 10);  // default '10'

  using Base::f2;
  virtual void f2 (int);
};

void bar ()
{
  Derived d;
  Base & b (d);
  d.f1 ();   // '10' used
  b.f1 ();   // '0' used

  d.f2 ();   // f1(int) called with '0' 
  b.f2 ();   // f1(int) called with '0'
}
  

There is only one situation where a default really needs to be used, and that is on a constructor. It is not possible to call one constructor from another, and so this technique does not work in that case.

Tense answered 29/6, 2009 at 18:5 Comment(3)
Some people think a default-param is less horrible than a giant multiplication of overloads.Shelby
Maybe at the end, you ment: d.f2(); // f2(int) called with '0' b.f2(); // f2(int) called with '0'Egress
On the last statement: from C++ 11 onwards, you can use delegating constructors to call one constructor from the other to avoid code duplication.Profess
B
32

There still is the old C way of providing optional arguments: a pointer that can be NULL when not present:

void write( int *optional = 0 ) {
    if (optional) *optional = 5;
}
Bothwell answered 19/1, 2010 at 15:30 Comment(1)
I really like this method, very short and simple. Super practical if sometimes you want to return some additional info, statistics, etc. that you usually dont need.Alpine
P
11

This little template will help you:

template<typename T> class ByRef {
public:
    ByRef() {
    }

    ByRef(const T value) : mValue(value) {
    }

    operator T&() const {
        return((T&)mValue);
    }

private:
    T mValue;
};

Then you'll be able to:

virtual const ULONG Write(ULONG &State = ByRef<ULONG>(0), bool sequence = true);
Prospectus answered 29/10, 2011 at 3:50 Comment(2)
Where does the instantiation of ByRef live, memory-wise? Isn't it a temporary object that would get destroyed upon leaving some scope (like the constructor)?Belike
@AndrewCheong Its entire intent is to be constructed in-spot and destructed when the line is complete. It's a means of exposing a reference for the duration of a call so that a default parameter can be provided even when it expects a reference. This code is used in an active project and functions as expected.Prospectus
S
8

There are two reasons to pass an argument by reference: (1) for performance (in which case you want to pass by const reference) and (2) because you need the ability to change the value of the argument inside the function.

I highly doubt that passing an unsigned long on modern architectures is slowing you down too much. So I'm assuming that you're intending to change the value of State inside the method. The compiler is complaining because the constant 0 cannot be changed, as it's an rvalue ("non-lvalue" in the error message) and unchangeable (const in the error message).

Simply put, you want a method that can change the argument passed, but by default you want to pass an argument that can't change.

To put it another way, non-const references have to refer to actual variables. The default value in the function signature (0) is not a real variable. You're running into the same problem as:

struct Foo {
    virtual ULONG Write(ULONG& State, bool sequence = true);
};

Foo f;
ULONG s = 5;
f.Write(s); // perfectly OK, because s is a real variable
f.Write(0); // compiler error, 0 is not a real variable
            // if the value of 0 were changed in the function,
            // I would have no way to refer to the new value

If you don't actually intend to change State inside the method you can simply change it to a const ULONG&. But you're not going to get a big performance benefit from that, so I would recommend changing it to a non-reference ULONG. I notice that you are already returning a ULONG, and I have a sneaky suspicion that its value is the value of State after any needed modifications. In which case I would simply declare the method as so:

// returns value of State
virtual ULONG Write(ULONG State = 0, bool sequence = true);

Of course, I'm not quite sure what you're writing or to where. But that's another question for another time.

Scarlatti answered 29/6, 2009 at 21:50 Comment(0)
S
7

No, it's not possible.

Passing by reference implies that the function might change the value of the parameter. If the parameter is not provided by the caller and comes from the default constant, what is the function supposed to change?

Sabellian answered 29/6, 2009 at 18:10 Comment(1)
The traditional FORTRAN way would have been to change the value of 0, but that doesn't happen in C++.Gnosis
O
6

You cannot use a constant literal for a default parameter for the same reason you cannot use one as a parameter to the function call. Reference values must have an address, constant references values need not (ie they can be r-values or constant literals).

int* foo (int& i )
{
   return &i;
}

foo(0); // compiler error.

const int* bar ( const int& i )
{
   return &i;
}

bar(0); // ok.

Ensure that you're default value has an address and you're fine.

int null_object = 0;

int Write(int &state = null_object, bool sequence = true)
{
   if( &state == &null_object )
   {
      // called with default paramter
      return sequence? 1: rand();
   }
   else
   {
      // called with user parameter
      state += sequence? 1: rand();
      return state;
   }
}

I've used this pattern a few times where I had a parameter that could be a variable or null. The regular approach is to have the user pass in a pointer this is case. They pass in a NULL pointer if they don't want you to fill in the value. I like to null object approach. It makes the callers life easier without terribly complicating the callee code.

Oca answered 29/6, 2009 at 21:3 Comment(1)
IMHO, this style is quite "smelly". The only time a default argument is ever really justifiable is when it's used in a constructor. In every other case function overloads provide exactly the same semantics without any of the other problems associated with defaults.Tense
M
1

I think not, and the reason is that default values are evaluated to constants and values passed by reference must be able to change, unless you also declare it to be constant reference.

Merl answered 29/6, 2009 at 18:9 Comment(1)
Default values are not "evaluated as constants".Bolo
D
1

Another way could be the following:

virtual const ULONG Write(ULONG &State, bool sequence = true);

// wrapper
const ULONG Write(bool sequence = true)
{
   ULONG dummy;
   return Write(dummy, sequence);
}

then the following calls are possible:

ULONG State;
object->Write(State, false); // sequence is false, "returns" State
object->Write(State); // assumes sequence = true, "returns" State
object->Write(false); // sequence is false, no "return"
object->Write(); // assumes sequence = true, no "return"
Deepsea answered 31/1, 2012 at 8:44 Comment(0)
F
1
void f(const double& v = *(double*) NULL)
{
  if (&v == NULL)
    cout << "default" << endl;
  else
    cout << "other " << v << endl;
}
Fandango answered 18/10, 2013 at 16:11 Comment(1)
It works. Basically, it is to access the value-at-reference for checking the NULL pointing reference (logically there is no NULL-reference, only that what you point is NULL). Atop, if you are using some library, which operates on "references", then there will generally be some APIs like "isNull()" for doing the same for the library specific reference variables. And is suggested to use those APIs in such cases.Clubby
B
1

In case of OO... To say that a Given Class has and "Default" means that this Default (value) must declared acondingly an then may be usd as an Default Parameter ex:

class Pagination {
public:
    int currentPage;
    //...
    Pagination() {
        currentPage = 1;
        //...
    }
    // your Default Pagination
    static Pagination& Default() {
        static Pagination pag;
        return pag;
    }
};

On your Method ...

 shared_ptr<vector<Auditoria> > 
 findByFilter(Auditoria& audit, Pagination& pagination = Pagination::Default() ) {

This solutions is quite suitable since in this case, "Global default Pagination" is a single "reference" value. You will also have the power to change default values at runtime like an "gobal-level" configuration ex: user pagination navigation preferences and etc..

Baumbaugh answered 14/5, 2014 at 14:25 Comment(0)
B
1

It's possible with const qualifier for State:

virtual const ULONG Write(const ULONG &State = 0, bool sequence = true);
Banditry answered 13/12, 2014 at 1:11 Comment(1)
It's pointless and even ridiculous to pass a long as a const ref, and doesn't achieve what the OP wants.Acrostic
P
1

Upgrading our code base from C++17 to C++20 using MSVC, Microsoft now finally enforces this rule.

The semantics for void foo(int& optOutParam = 0) would be optOutParam is an optional output parameter. Often I see this concrete example, where multiple output parameters are required:

bool Initialise(Window& wnd, Context& ctxt, std::string& error = "") {
// ...
}

The return value indicates whether the function succeeded, while error contains the reason why it might have failed. Unfortunately std::optional is explicitly forbidden for references, or this would be a great solution, too.

std::optional<int&> opt; // compiler error

A good way to refactor is to actually have the function return all values and refrain from using reference parameters as output.

std::tuple<Window, Context, std::string> Initialise();

void modernCpp() {
  auto[wnd, ctxt, errorMsg] = Initialise();
  if(!errorMsg.empty()) {
    exit(1);
  }
}

void olderCpp() {
  Window wnd;
  Context ctxt;
  std::string errorMsg;
  std::tie(wnd, ctxt, errorMsg) = Initialise();

  // or
  tuple<Window, Context, std::string> result = Initialise();
}

Unfortunately this means having to refactor every call-site as well, which might be a huge job in large code bases. To deal with that case I use an r-value overload, that discards the error message. No call-site has to be changed then.

bool Initialise(Window& wnd, Context& ctxt, std::string& error) {
  // default value removed
}
bool Initialise(Window& wnd, Context& ctxt, std::string&& discardedErrorMsg = "") {
  // call overload above
  return Initialise(wnd, ctxt, discardedErrorMsg);
}

This has one major draw-back, namely gathering the info for error message might be very costly. Asking a database what went wrong, for example, may require another network trip. Bear in mind though, that the original function has the same issue, so this is a great micro-optimisation chance.

For the vast majority of new functions with multiple out parameters, I use multiple return values.

Pinfish answered 31/10, 2023 at 8:58 Comment(0)
G
0
void revealSelection(const ScrollAlignment& = ScrollAlignment::alignCenterIfNeeded, bool revealExtent = false);
Geithner answered 27/4, 2011 at 12:58 Comment(0)
M
0

There is also rather dirty trick for this:

virtual const ULONG Write(ULONG &&State = 0, bool sequence = true);

In this case you have to call it with std::move:

ULONG val = 0;
Write(std::move(val));

It is only some funny workaround, I totally do not recommend it using in real code!

Mccloskey answered 12/10, 2016 at 22:42 Comment(0)
O
0

I have a workaround for this, see the following example on default value for int&:

class Helper
{
public:
    int x;
    operator int&() { return x; }
};

// How to use it:
void foo(int &x = Helper())
{

}

You can do it for any trivial data type you want, such as bool, double ...

Occlude answered 1/12, 2018 at 19:6 Comment(0)
T
0

Define 2 overload functions.

virtual const ULONG Write(ULONG &State, bool sequence = true);

virtual const ULONG Write(bool sequence = true)
{
    int State = 0;
    return Write(State, sequence);
}
Telex answered 3/8, 2020 at 9:8 Comment(0)
K
-4

virtual const ULONG Write(ULONG &State = 0, bool sequence = true);

The answer is quite simple and I am not so good on explaining but if you want to pass a default value to a non-const parameter which probably will be modified in this function is to use it like this:

virtual const ULONG Write(ULONG &State = *(ULONG*)0, bool sequence =
> true);
Kora answered 14/6, 2012 at 13:41 Comment(1)
Dereferencing a NULL pointer is illegal. This may work in some cases, but it's illegal. Read more here parashift.com/c++-faq-lite/references.html#faq-8.7Shandra

© 2022 - 2025 — McMap. All rights reserved.