Skip to main content
added 352 characters in body
Source Link
Martin Ba
  • 39.4k
  • 35
  • 204
  • 374

I find that "hard to use incorrectly" is quite a long shot from "impossible to use incorrectly". And "normal" const-ref parameters, just like normal data members are hard-to-use-incorrectly (as far as C++ goes). const& members on the other hand are easy-to-use-incorrectly. Others seem to disagree: See the answer below. Alternatives still welcome.

I find that "hard to use incorrectly" is quite a long shot from "impossible to use incorrectly". And "normal" const-ref parameters, just like normal data members are hard-to-use-incorrectly (as far as C++ goes). const& members on the other hand are easy-to-use-incorrectly. Others seem to disagree: See the answer below. Alternatives still welcome.

added 673 characters in body
Source Link
Martin Ba
  • 39.4k
  • 35
  • 204
  • 374

Appendix: Why using const& members is unsafe:

struct HasConstRef {
    std::string const& member;
};

void f(HasConstRef const& arg) {
    std::cout << arg.member << "\n";
}

HasConstRef arg_producer() {
    HasConstRef result = { "there be dragons" };
    return result; // UB
}

void f_call() {
    f(arg_producer()); // *boom*, no diagnostic required
}

While I totally agree with the current answer that a const-ref-membered struct can be used correctly, it is also incredibly easy to use incorrectly without any help from the compiler. I would rather not do this.


Appendix: Why using const& members is unsafe:

struct HasConstRef {
    std::string const& member;
};

void f(HasConstRef const& arg) {
    std::cout << arg.member << "\n";
}

HasConstRef arg_producer() {
    HasConstRef result = { "there be dragons" };
    return result; // UB
}

void f_call() {
    f(arg_producer()); // *boom*, no diagnostic required
}

While I totally agree with the current answer that a const-ref-membered struct can be used correctly, it is also incredibly easy to use incorrectly without any help from the compiler. I would rather not do this.

Source Link
Martin Ba
  • 39.4k
  • 35
  • 204
  • 374

C++ struct as function argument vs. multiple const ref parameters vs. C++ core guidelines vs. performance?

I'm currently trying to decide whether to "structify" a rather long parameter set:

void fooCopy1(std::string const& source, std::string const& destination, std::string const& filter, std::string const& temp);

to this:

struct FooCopyArgs {
    std::string source;
    std::string destination;
    std::string filter;
    std::string temp;
};
void fooCopy2(FooCopyArgs const& args);

As already answered in two other questions:

refactoring this could have several readability/maintainability advantages. For a bunch of these see the linked questions.

In general however I see one "big" problem with this approach in C++ specifically, and that is that the strings will always have to be copied before the call to this function, instead of using const& parameters.

This would be against C++ Core Guideline F.16 "pass cheaply-copied types by value and others by reference to const".

That is, non-cheap readonly parameters that would normally be passed by const ref, would need to be copied into the struct, which would be a general pessimization.

(Yes, the struct itself would be passed by const ref, but the struct data members would need to copied first.)

Example:

const string temp = ...;
const string filter = ...;
...
fooCopy2({"sourceItem", "targetItem", filter, temp});

For "sourceItem", that is a locally defined parameter value, it would not matter. However, for the passed down args filterand temp we would have an extraneous copy that could be avoided by the plain const& approach.

Disclaimer: Obviously, in 99% of cases the performance impact won't even be observable in the final application, but still it leaves a bad taste, esp. in the context of some such "fundamental" rule as F.16.

Question : Is there any clever way around this problem, that is:

  • have a safe struct as parameter type (const& members are not safe; extremely prone to dangling references)
  • avoid extraneous copy of non-cheap types
  • keep composability if severeal functions use this pattern