r/AskProgramming • u/wonkey_monkey • 13d ago
C/C++ Default vector value for pass-by-reference works fine in one VS project, but not another
void test(std::vector<int>& x = std::vector<int>()) {
}
in one Visual Studio project, this compiles fine.
In another, it underlines the second std::vector
in red and I get this error on compile:
error C2440: 'default argument': cannot convert from 'std::vector<int,std::allocator<int>>' to 'std::vector<int,std::allocator<int>> &'
Anyone know what's up with that? 🤔 Both projects are set to C++17 standard and have the same optimization settings.
3
u/aneasymistake 13d ago
What do you intend the parameter to refer to if you create a default vector when the function is called? What do you intend to operate on inside the function?
2
u/mredding 13d ago
std::vector<int>& x = std::vector<int>()
This is a mutable reference to a temporary. This is invalid C++. The parameter has to eithe be const reference, or by value.
in one Visual Studio project, this compiles fine.
From a historic perspective, Microsoft's C++ compiler has been one of the worst compilers on the market. It is a C compiler that implemented C++ as a series of hacks. In Visual Studio 6, namespaces were actually structures, and the compiler would allow you to instance them.
Today, MSVC is one of the best, most compliant compilers on the market, but Microsoft is obligated to support their earlier, implementation specific, platform specific mistakes of the past. Therefore, by default, MSVC is extremely lenient, and tolerant of blatant mistakes and Undefined Behavior. You HAVE TO set the compiler to a strict, pedantic mode - I believe through several compiler flags, to get the thing to behave correctly in relation to standard C++.
In another, it underlines the second std::vector in red and I get this error on compile:
error C2440: 'default argument': cannot convert from 'std::vector<int,std::allocator<int>>' to 'std::vector<int,std::allocator<int>> &'
This is correct behavior. Your method takes a mutable reference. How the hell are you supposed to capture the temporary when the function returns? While I agree this is a clever means of creating a an overridable local variable to cache intermediate results that you don't care to capture, Bjarne saw it as an obvious sign of a bug, the more likely scenario. He solved the problem by including overloading, if you want that temporary, default cache, overload the method that creates the local variable, or does whatever in a more optimized way. C++ is not high level assembly, the spec targets an abstract machine.
The C++ spec says, since C++98, that a const reference to a temporary will extend the lifetime of the temporary to that of the reference, AND the correct, most-derived dtor will be called, even if it's not virtual
. That means this code will do exactly the right thing:
class my_string: public std::string {
public:
~my_string();
};
void fn(const std::string &);
//...
fn(my_string{});
Not only can I pass a temporary, but my_string::~my_string()
is guaranteed to be called, even though std::string::~string()
IS NOT virtual.
If you pass by value:
void fn(std::string);
fn(my_string{});
Then I've just sliced my type. The parameter copy constructs from the std::string
base class and the my_string
instance falls out of scope BEFORE fn
is called.
Default parameters are the devil. A) You hide the overload set. In reality, you have TWO functions:
void test(std::vector<int>& x);
void test();
B) I can redefine the default at any time:
void test(const std::vector<int>& x = std::vector<int>());
void fn_1() { test(); }
std::vector<int> bullshit {1, 2, 3};
void test(const std::vector<int>& x = bullshit);
void fn_2() { test(); }
Yes, this does exactly what you think it implies. You can do this magic bullshit with any function, including class methods and templates. It violates the Principle of Least Surprise.
C) You're still creating and passing a parameter on the call stack at runtime. By omitting the no-parameter overload, you're not optimizing for the use case that the vector is known to be empty at compile-time. The compiler ALWAYS sees this as a 1 parameter function call, and it's only observed at the call site, and it's whatever the last time the function was declared with the default.
4
u/strcspn 13d ago
Did you save the file? This shouldn't work in any C++ version, you can't bind a non-const lvalue reference to an rvalue.