void foo(out_ref<int> o) { o = 10; }
void main()
{
int x;
foo(x);
cout << x << endl;
}
It would be useful for c++ to have an idiomatic way to express strictly output parameters.
functions should not have "strictly output parameters". I can understand taking input/output parameters (like a `vector` that you add data to), but there's no reason to have "strictly output parameters" in C++.
tuple<int, int, int> calculate_averages(vector<int> values);
struct averages_out
{
int mean;
int median;
int mode;
};
averages_out calculate_averages(vector<int> values);
void calculate_averages(vector<int> values, int& mean, int& median, int& mode);
void calculate_averages(vector<int> values, output_parameter<int> mean, output_parameter<int> median, output_parameter<int> mode);
For case #2, Since the three results aren't really related, it feels clumsy to have a separate struct which is used only in one function.
I can think of one case where output parameters are arguably a better choice than returning a struct/tuple: when you're returning multiple values of the same type, which aren't inherently related to each other.If they're different types, you can use a tuple -- the type parameters will give you info about what's being returned. If the data is inherently related, then it arguably deserves its own type.Consider the following example, though:Case #1
tuple<int, int, int> calculate_averages(vector<int> values);
Case #2
struct averages_out
{
int mean;
int median;
int mode;
};
averages_out calculate_averages(vector<int> values);Case #3
void calculate_averages(vector<int> values, int& mean, int& median, int& mode);Case #4
void calculate_averages(vector<int> values, output_parameter<int> mean, output_parameter<int> median, output_parameter<int> mode);Case #1 would be the clear winner, if tuples had named parameters somehow. As it stands, I'd be hesitant to go this route even if I documented the return type thoroughly.For case #2, Since the three results aren't really related, it feels clumsy to have a separate struct which is used only in one function.Case #3 seems pretty clear that they are supposed to be output parameters, and whose initial values are not for input. However, this isn't explicit.Case #4 seems the least bad to me. Output parameters aren't great, but the fact that the function definition describe its usage is a big win, IMO.
int mean, median, mode;
calculate_averages(vec, &mean, &median, &mode);
auto[mean, median, mode] = calculate_averages(vec);
auto stats = calculate_averages(vec);
I agree that #2 is the right solution. One change that might make #2 more palatable would be to allow structs to be declared as part of a function's return type. MSVC seems to accept this code but I don't think it is conforming.struct { int mean; int median; int mode; } calc_averages(vector<int>);
void foo([[out]] int &o) { o = 10; }
I can think of one case where output parameters are arguably a better choice than returning a struct/tuple: when you're returning multiple values of the same type, which aren't inherently related to each other.
array<BigPOD>
),
consider allocating it on the free store and return a handle (e.g.,
unique_ptr
), or
passing it in a reference to non-const
target object to fill (to be used as an
out-parameter).struct Package { // exceptional case: expensive-to-move object char header[16]; char load[2024 - 16]; };
If copy elision doesn't applies to fill Package fill(); // Bad: large return value void fill(Package&); // OK
Package pkg;
fill(pkg)
void fill(out_param<Package>); // Better?
Package pkg;
fill(out(pkg))
Package fill(); // Ok: copy elision
void fill(Package&); // Bad
If they're different types, you can use a tuple -- the type parameters will give you info about what's being returned. If the data is inherently related, then it arguably deserves its own type.
Consider the following example, though:
Case #1
tuple<int, int, int> calculate_averages(vector<int> values);
Case #2
struct averages_out
{
int mean;
int median;
int mode;
};
averages_out calculate_averages(vector<int> values);
Case #3
void calculate_averages(vector<int> values, int& mean, int& median, int& mode);
Case #4
void calculate_averages(vector<int> values, output_parameter<int> mean, output_parameter<int> median, output_parameter<int> mode);
Case #1 would be the clear winner, if tuples had named parameters somehow. As it stands, I'd be hesitant to go this route even if I documented the return type thoroughly.
For case #2, Since the three results aren't really related, it feels clumsy to have a separate struct which is used only in one function.
Case #3 seems pretty clear that they are supposed to be output parameters, and whose initial values are not for input. However, this isn't explicit.
Case #4 seems the least bad to me. Output parameters aren't great, but the fact that the function definition describe its usage is a big win, IMO.
Note, I think the recommendations given here are good, but I don't think they apply to the above example: http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-out-multi
istream& operator>>(istream& is, string& s); // much like std::operator>>()
In this case we are forced by the standard library design.Also note, I thought about it, and I think Jake is right that a non-const reference is clearly an input/output parameter. A separate type for this doesn't seem useful to me.
I agree that #2 is the right solution. One change that might make #2 more palatable would be to allow structs to be declared as part of a function's return type. MSVC seems to accept this code but I don't think it is conforming.struct { int mean; int median; int mode; } calc_averages(vector<int>);
One-off return types often occur... the first time you use them. Then, the next time you use them, which is often the case later down the line, you're tied to using whatever approach you used the first time unless you want to either be inconsistent or introduce breaking changes.I think of this as a DRY-trap. If you are to keep repeated code to a minimum, you need to make decisions early on such that you don't end up forced into repeating code. For example, with strictly output parameters, you need to create those variables before the function is called (and if you call this function in several places, this means repeating). If you want to use an unnamed struct, you have to do the same thing unless you auto it out (but then you might as well just name that struct and be done with the matter).This argument only applies to the justification "it's only used once". There are other justifications, such as resource usage (covered by Vincente) and scope control ({ cheap a; {expensive b; do_something(a,b); handle_b(b); } use_lots_of_resources(); handle_a(a); } ) for which I have no rebuttal. They're completely valid.
cheap a;
{
auto [c, b] = do_something();
handle_b(b);
a = c;
}
I'd sooner go for a proposal that somehow adds some fairy dust to make returning equally as effective in these outlying cases, than one which standardises (and thus legitimizes) use of out parameters for the foreseeable future. Though if the former is even possible, whoever comes up with it should probably prepare to receive a lot of hate mail from compiler writers.