Search code examples
c++referencepass-by-referencetemporaryconst-reference

Is using a reference parameter with default value good practice?


I have the following code:

#include <string>
#include <iostream>

void f(const std::string& s = "")
{
  std::cout << "\"" << s << "\"" << std::endl;
}

int main()
{
  std::string s1 = "qwe";
  f();
  f("asd");
  f(s1);
}

How bad (if at all) are the calls with the temporary and without the parameters?

As far as I know this compiles only due to the fact that const reference prolongs the life of the temporary until the end of a method http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/

Trying to compile the same example without the const next to s parameter fails.

#include <string>
#include <iostream>

void f(std::string& s = "")
{
  std::cout << "\"" << s << "\"" << std::endl;
}

int main()
{
  std::string s1 = "qwe";
  f();
  f("asd");
  f(s1);
}

Compilation

g++-5 -O3 -Wall --std=c++11 main.cpp  && ./a.out
main.cpp:4:27: error: invalid initialization of non-const reference of type ‘std::string& {aka std::basic_string<char>&}’ from an rvalue of type ‘std::string {aka std::basic_string<char>}’
 void f(std::string& s = "")
                           ^
In file included from /usr/include/c++/5/string:52:0,
                 from main.cpp:1:
/usr/include/c++/5/bits/basic_string.h:2893:7: note:   after user-defined conversion: std::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]
       basic_string(const _CharT* __s, const _Alloc& __a = _Alloc());
       ^
main.cpp: In function ‘int main()’:
main.cpp:12:5: error: invalid initialization of non-const reference of type ‘std::string& {aka std::basic_string<char>&}’ from an rvalue of type ‘std::string {aka std::basic_string<char>}’
   f();
     ^
In file included from /usr/include/c++/5/string:52:0,
                 from main.cpp:1:
/usr/include/c++/5/bits/basic_string.h:2893:7: note:   after user-defined conversion: std::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]
       basic_string(const _CharT* __s, const _Alloc& __a = _Alloc());
       ^
main.cpp:4:6: note: in passing argument 1 of ‘void f(std::string&)’
 void f(std::string& s = "")
      ^
main.cpp:13:10: error: invalid initialization of non-const reference of type ‘std::string& {aka std::basic_string<char>&}’ from an rvalue of type ‘std::string {aka std::basic_string<char>}’
   f("asd");
          ^
In file included from /usr/include/c++/5/string:52:0,
                 from main.cpp:1:
/usr/include/c++/5/bits/basic_string.h:2893:7: note:   after user-defined conversion: std::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]
       basic_string(const _CharT* __s, const _Alloc& __a = _Alloc());
       ^
main.cpp:4:6: note:   initializing argument 1 of ‘void f(std::string&)’
 void f(std::string& s = "")

Solution

  • It's not a horrible practice, but it's generally better to provide overloads:

    void f(std::string const& s) { std::cout << "\\" << s << "\\\n"; }
    
    void f() { f(""); }
    

    It avoids some language features that end up being confusing to many people. For example, what does this print?

    struct base { virtual void f(int i = 42) { std::cout << i; } };
    
    struct derived : base { void f(int i = 19) { std::cout << i; }};
    
    int main() { base * b = new derived(); b->f(); }
    

    There are also ambiguity errors that can come up when you're using default parameters that don't when you use overloads.

    As far as const references in particular, that doesn't really matter much. The default value binds to reference for the lifetime of the function call. It has no effect at all really. You might get better results using values sometimes when the compiler can perform certain optimizations that are not possible with reference parameters, but generally it's not something to be concerned with.

    Of course, this doesn't work with non-const references because they don't bind to temporaries.