Passing optional parameter by reference in c++
Asked Answered
D

13

55

I'm having a problem with optional function parameter in C++

What I'm trying to do is to write function with optional parameter which is passed by reference, so that I can use it in two ways (1) and (2), but on (2) I don't really care what is the value of mFoobar.

I've tried such a code:

void foo(double &bar, double &foobar = NULL)
{
   bar = 100;
   foobar = 150;
}

int main()
{
  double mBar(0),mFoobar(0);

  foo(mBar,mFoobar);              // (1)
  std::cout << mBar << mFoobar;

  mBar = 0;
  mFoobar = 0;

  foo(mBar);                     // (2)
  std::cout << mBar << mFoobar;

  return 0;
}

but this line doesn't compile

void foo(double &bar, double &foobar = NULL)

with message :

error: default argument for 'double& foobar' has type 'int'

Is it possible to solve it without function overloading?

Deathwatch answered 12/5, 2010 at 5:49 Comment(5)
Of course not - even if you could pass a NULL reference, what do you think would happen within the function when you tried to assign a value to it ?Olfactory
Compilation error is not a crash.Borden
Your code is not "crashing", it is getting a compilation error. A crash can only occur after you have fully built your program and attempt to run it.Kenishakenison
possible duplicate of Default value to a parameter while passing by reference in C++Carner
Anyone in this situation should really swallow their pride and aesthetic preferences - and use a *const pointer. It's an indirection, just like a reference - but with nullability/testability. Why complicate things with dummy variables, the bloat of Boost, etc - when the language provides a core features that fits the bill precisely as far as I can tell?Dumbstruck
B
49

The default argument of a (mutable) reference must be an l-value. The best I can think of, without overloading, is

static double _dummy_foobar;
void foo(double &bar, double &foobar = _dummy_foobar)
Borden answered 12/5, 2010 at 5:53 Comment(8)
It may be worth defining lvalue for the asker. (Essentially, and informally, "something that can be assigned to.")Tridimensional
@Tridimensional That definition is common, but wrong. Some rvalues can also be assigned to, for example std::string("hello") = world, and some lvalues cannot be assigned to, for example C-arrays and objects without an assignment operator. (Also, const lvalues cannot be assigned to, of course, but that seems to be well understood.)Inguinal
This can introduce subtle thread safety issues, if the dummy object is ever read.Dissection
For further safety, define _dummy_foobar in an internal namespace and do optional operations with foobar in the function foo if &foobar!=&internal::_dummy_foobar to prevent _dummy_foobar from being accessed.Sake
sure, it answers the question to the limited degree that's possible, but i question the motivation behind the question... why bother with the mess of allocating a useless dummy variable when C++ already provides an alternative indirection that is nullable/checkable? it's called a pointer!Dumbstruck
While this is a fine answer, I fail to see the advantage vs. using a pointer. If you have an optional pointer parameter, and you try to access it, you get a runtime error. If you use a dummy reference, you don't. You may be interacting with a dummy object, and not know it. I'd rather have a runtime error that notifies me that I'm doing something wrong. Now, you might compare it to the dummy variable, and see if the parameter has indeed been specified; but then you might test if the pointer is nonzero. The point is, detecting the error is for those cases where you FORGOT to test.Profuse
I am, however, open to suggestions as to real advantages this might have.Profuse
I like this answer, because I really like the ability to pass variable by reference. Not that it would happen in properly tested code, but were there a bug a pointer after could inadvertently be incremented or moved, which could result in reading or writing to the wrong (or even illegal) memory. A reference is like an immovable pointer, safe even from the easily mistaken placement of a 'const' keyword. For the sacrifice of the memory used by a single 'dummy' variable, the convenience and inherent safety of a reference arg is preserved. Just name the dummy with leading underscores.Rattlebrained
P
54

Why can't you use function overloading? Surely it's the easiest solution to your problem?

void foo(double &bar, double &foobar) 
{ 
   bar = 100; 
   foobar = 150; 
}

void foo(double &bar) 
{ 
   double foobar = 0.0;
   foo(bar, foobar);
}
Protectionism answered 12/5, 2010 at 5:56 Comment(4)
I can but I just wanted to know if it is possible to solve the problem without overloading. Imagine waking up on an island where people really hate overloading :).Deathwatch
@Deathwatch I would guess that the "I hate default parameters" island has much more inhabitants ;-)Inguinal
overloading function should be the better alternative to a dummy bool lingering in the class, especially when there are other classes inheriting from the base class.Plaque
@In silico What if foo has more lines of code and the optional ref variable foobar makes only extra 4 lines of code in the first overload void foo(double &bar) ? then this idea, will be definitely a code duplication, right?Pender
B
49

The default argument of a (mutable) reference must be an l-value. The best I can think of, without overloading, is

static double _dummy_foobar;
void foo(double &bar, double &foobar = _dummy_foobar)
Borden answered 12/5, 2010 at 5:53 Comment(8)
It may be worth defining lvalue for the asker. (Essentially, and informally, "something that can be assigned to.")Tridimensional
@Tridimensional That definition is common, but wrong. Some rvalues can also be assigned to, for example std::string("hello") = world, and some lvalues cannot be assigned to, for example C-arrays and objects without an assignment operator. (Also, const lvalues cannot be assigned to, of course, but that seems to be well understood.)Inguinal
This can introduce subtle thread safety issues, if the dummy object is ever read.Dissection
For further safety, define _dummy_foobar in an internal namespace and do optional operations with foobar in the function foo if &foobar!=&internal::_dummy_foobar to prevent _dummy_foobar from being accessed.Sake
sure, it answers the question to the limited degree that's possible, but i question the motivation behind the question... why bother with the mess of allocating a useless dummy variable when C++ already provides an alternative indirection that is nullable/checkable? it's called a pointer!Dumbstruck
While this is a fine answer, I fail to see the advantage vs. using a pointer. If you have an optional pointer parameter, and you try to access it, you get a runtime error. If you use a dummy reference, you don't. You may be interacting with a dummy object, and not know it. I'd rather have a runtime error that notifies me that I'm doing something wrong. Now, you might compare it to the dummy variable, and see if the parameter has indeed been specified; but then you might test if the pointer is nonzero. The point is, detecting the error is for those cases where you FORGOT to test.Profuse
I am, however, open to suggestions as to real advantages this might have.Profuse
I like this answer, because I really like the ability to pass variable by reference. Not that it would happen in properly tested code, but were there a bug a pointer after could inadvertently be incremented or moved, which could result in reading or writing to the wrong (or even illegal) memory. A reference is like an immovable pointer, safe even from the easily mistaken placement of a 'const' keyword. For the sacrifice of the memory used by a single 'dummy' variable, the convenience and inherent safety of a reference arg is preserved. Just name the dummy with leading underscores.Rattlebrained
D
44

Don't use references for optional parameters. There is no concept of reference NULL: a reference is always an alias to a particular object.

Perhaps look at boost::optional or std::experimental::optional. boost::optional is even specialized for reference types!

void foo(double &bar, optional<double &> foobar = optional<double &>())
Dissection answered 12/5, 2010 at 5:57 Comment(1)
One should use std::optional< std::reference_wrapper<T> > for the standard version. See std::optional specialization for reference types.Scarlatti
T
11

Another way to do this is to use pointers instead of references. This provides the semantics that you want without overloading. (Personally, I'd probably go with overloading.)

void foo(double* bar, double* foobar = 0)
{
   if (bar) *bar = 100;
   if (foobar) *foobar = 150;
}

   // ...

   foo(&mBar, &mFoobar);

   // ...

   foo(&mBar);

   // ...
Tridimensional answered 12/5, 2010 at 6:0 Comment(2)
which leads to a bunch of != || == nullptr sprinkled all over the universe and a massive headache + delay in actually getting to work when the programmer reading your 20,000 line library with nullptr and ** * * ** ** sprinkled everywhere can't figure out who owns what, what needs to be specified and what doesn't, and has a mild stroke.Waterside
I hear what you're saying, but you're overselling it more than a little. Like I said, my preference is typically to use overloading, but I often call from those public reference-based overloaded functions into a common private function to do the actual work, which I'm more than happy passing pointers to and doing my null checks in.Tridimensional
F
7

To do this safely with the standard library, you'll need to use std::optional in conjunction with std::reference_wrapper. Optional references of the form std::optional<T&> are illegal in C++17.

#include <optional>
#include <functional>

void foo(double& bar, std::optional<std::reference_wrapper<double>> foobar = {})
{
    if(foobar) // If the user has passed a reference
    {
        foobar->get() = 1.0; // Assign values to the reference
    }
}

This is completely transparent to the callee. You can call such a function as you would normally:

double a {}, b {};
foo(b, a);
std::cout << a; // Prints 1.0;

The advantage of this approach is that it has a null value to indicate if the user has actually passed a reference or not. With -O2/-O3, it also optimises away pretty neatly with no runtime cost.

Frederiksen answered 21/6, 2021 at 14:14 Comment(1)
Nicest solution when C++17 compiler is available.Scheming
D
5

Here is another crazy way that does not result in memory leaks, which you should never use in real life, but seems to be standard compliant at first glance and compiles with Visual C++ 2008 & g++ 3.4.4 under Cygwin:

void foo(double &bar, double &foobar = (*((double*)0)))
{
   bar = 100;
   double* pfoobar = &foobar;
   if (pfoobar != 0)
       foobar = 150;
}

To reiterate: DON'T DO THIS! THERE ARE BETTER OPTIONS! OVERLOADING CAN BE YOUR FRIEND! But yeah, you can do it if you're foolish and careful. :)

Dao answered 23/3, 2011 at 18:15 Comment(7)
References must designate an object at the point of binding refMaighdlin
it compiles, maybe... but what does it do? did you try running it? this is not "standard compliant" at all, so the compiler is free to treat it as UB and do absolutely anything - such as removing the thing that's meant to happen if it's not NULL, or the entire function call, or blowing up your toaster. who cares?Dumbstruck
One, yes I did try running it. I alluded to that with my reference to two separate compilers above. Two, I guess mea culpa for playing a little fast and loose with the concept of "standards compliance" almost five years ago. At no point did I quote chapter and verse, and at no point did I say it was defined behavior. Three, I said repeatedly that there were better ways. Maybe you didn't see the text above and below the sample code I posted. I think the original point was fairly clear that there are lots of bad things that can be done syntactically but that should not be done.Dao
A constant value (for example, (double*)0 ) will be put into a read-only area of the program by the compilers, and any WRITE operation to the referenced constant value is illegal and will cause problems such as program crash.Jobie
You are correct (at least for any modern compiler on a system supporting memory protection). This is a 9 year old post. Standard syntax supports it, but every compiler I've tried with the exact code sample given above does "work" even if it is undefined behavior. By checking the address of the bound reference and refusing to modify it if it is null, To re-reiterate my last sentence: DON'T DO THIS! It is not advisable. There are better ways to do it. But compilers at the time (2011) did accept the code and did what one might expect.Dao
Totally works in Visual Studio! I'd like to submit another answer to explain why this is quite useful.Playgoer
This will not work as expected on clang (clang will likely just delete this function and refuse to call it). It's undefined behavior for a reason. It might "work" on Visual Studio for now, but this is a landmine waiting to kill you. Source: Have stepped on this landmine in production code, caused major release miss.Balmy
S
2

It is much easier to use a pointer type and setting it to NULL than setting default/optional value for a reference parameter.

Solace answered 31/5, 2012 at 21:57 Comment(6)
It's much easier for you when your writing out your program. It's a confusing mess of "what's defined, who defined it, did someone define it then keep or hand over ownership, do i need to manage it?" for anyone else actually trying to use it.Waterside
On the other hand, references can be a confusing mess of "is this library function going to change my variable or not?" which can lead to thread safety issues, among other things. It's another case of C/C++ leaving you in the "evaluate for your particular situation" boat. Both can be useful tools in the toolbox.Gunnysack
@Bryan Shaw: That's why you have const references. Unless the library function takes a const reference, you should assume that it will change your variable. References should be preferred over pointers in most cases.Sake
@Sameer: I agree with you, but the problem is two-fold. First issue is far too many programmers don't understand that and don't use const the way it is meant to be used (too often its considered to mean "constant" rather than "read only"). So that leaves people using libraries in the situation where they DO have to assume variables will be changed and program around that assumption. Granted the same is true with pointers.Gunnysack
The other part is that its not always clear that you are dealing with a pass by reference rather than a pass by value. You have to look at the declaration. Of course if you are writing new code you would, but if a maintainer comes along they could miss that detail. At least with pointers you KNOW that's what you are dealing with, and you can still use const with pointers... Not saying one should never use pass by reference, just that it has downsides to consider.Gunnysack
@TechnikEmpire Maybe you don't like pointers, but your objections seem ranty, not relevant. The idea of mess is moot: a theoretical 'null reference' would require the same checks. The thing about ownership is a red herring when we can just (A) check the declaration and documentation around it - something you need to do when passing a non-pointer anyway, to determine whether it might be taken as a non-const reference... or (B) assume a raw pointer is non-owning, since we have the STL _ptr classes to signify ownership and should use those semantically.Dumbstruck
S
1
void foo(double &bar,
         double &foobar = const_cast<double &>(static_cast<const double &>(0.0)))
{
   bar = 100;
   foobar = 150;
}

What happens here is that we first create a temporary variable at value 0.0. Using static_cast, we obtain a reference, and the life of the temporary is extended. We cannot directly cast to a mutable at this stage. Since the original temporary is not marked as const, we can have a mutable reference by using const_cast, which is not advised to be used in general but suitable for this case. By assigning the result to a named variable called foobar, there occurs life extension, thus we can have an optional output parameter without function overloading.


I agree that the previous solution is ugly and too verbose. There is a better approach which has the same logic behind. With C++11, we can benefit from r-value reference (&&).

template <class T>
T& make_ref(T&& x) { return x; }

void foo(double &bar, double &foobar = make_ref(0.0))
{
   bar = 100;
   foobar = 150;
}

See understanding l-values and r-values for a good explanation with simple examples.

Scarlatti answered 23/10, 2020 at 14:21 Comment(1)
The community really hates code-only answers and casts them away in a fury. I would be glad if I saw these solutions when I first looked up this thread. Anyway, I decided to put an explanation and merge the deleted answer into this one a year later. Any constructive feedback is always welcome.Scarlatti
H
0

Speaking in terms of Object Oriented paradigm: If given class has and "Default", this Default must declared accordingly, and then may be used as an "Default Parameter" Ex:

class Pagination {
private:
    int currentPage;
public:

    //...
    Pagination() {
        currentPage = 1;
        //...
    }

    // your Default Pagination (Must be initialized before thread concurrency)
    static Pagination& Default() {
        static Pagination p; 
        return p;
    }
};

On your Method ...

     //...
     std::vector<User>
     findByFilter(User& audit, Pagination& p = Pagination::Default() ) {
     // ...

Edited: This solution is quite suitable since in this case it is an "global default" Pagination and a single "reference" value. You will also have the power to change default values such as navigation/display preferences and etc.

Edit2: spelling and fixing...

Heinrick answered 30/4, 2014 at 16:24 Comment(0)
R
0

Starting with C++17, you may use parameter pack to implement this without manual overload, and without runtime overhead, like this:

template <typename... OptArgType> void foo(double &bar, OptArgType&... foobar)
{
   static_assert(sizeof...(OptArgType) <= 1 && (std::is_same_v<OptArgType, double> && ...));
   bar = 100;
   ((foobar = 150), ...); // use fold expression for assignment
}

No "is this argument provided" check would be needed at runtime, the fold expression will expand to the code only for invocations actually passing the argument to the function.

Alternatively to the static_assert, the check for the argument correctness may be inplemented using std::enable_if.

Relation answered 4/10, 2021 at 10:20 Comment(0)
S
0

We want temporary materialization to occur.

Possible options:

  • bind a reference to a prvalue

    See my other answer.

  • perform member access

    double& foobar = *std::make_unique<double>(0.0);
    
  • perform array to pointer conversion

    double& foobar = std::array<double, 1>{ 0.0 }[0];
    
Scarlatti answered 7/8, 2023 at 10:17 Comment(1)
See an intuitive approach with std::optional which does not require extra allocation for the default argument.Scarlatti
S
-2

This is how I solved this question:

My original function didn't have a returned error string: bool MyClass::validateXML(const QString& fileName, const QUri& schemaUri);

I wanted to add the results of the validation in an error string so I implemented:

bool MyClass::validateXML(const QString& fileName, 
                    const QUri& schemaUri, 
                    QString& errorString = *(std::make_unique<QString>().get()));

This way, you can reference the errorString in validateXML without checking if it's valid, and no memory leaks.

Saltant answered 4/12, 2018 at 22:24 Comment(1)
brr, chilling thing to do.Why don't you just declare a QString and pass it? The code doesn't even compile anyway.Charge
L
-4

You can do this crazy way:

void foo(double &bar, double &foobar = (*(new double())))

P.S. - I know its not pleasant but its the way. Also be sure not to leave memory leaks! :))

Logomachy answered 12/5, 2010 at 5:59 Comment(4)
Even if it didn't cause undefined behaviour, what would be a possible mechanism for safely deleting the optional parameter in the cases where it was actually used?Inceptive
make it a smart_ptr and you're on!Pedroza
There is no way to avoid a leak - you can't tell inside the function whether the memory was allocated via the default argument or not, and there's no way outside the function to know the pointer. You're hosed (and leaking)!Januarius
This approach would work without leaking if you also enable C++ garbage collector. Alas, that was removed from the language in C++23.Pierro

© 2022 - 2024 — McMap. All rights reserved.