Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best viable overloaded function between std::reference_wrapper<const T> and T

Recently, I've decided to write a class storing a variant with reference_wrapper<const vector> and vector for having a choice either to own the value or having only a reference of it. That is, std::variant<vector<string>, reference_wrapper<const vector<string>>>.

The interesting part is what the variant stores depending on initialization. I did a small investigation, and it turned out, that in all cases vector<string> type wins, except for the case when passing via std::cref. The same applies to functions (somewhat expected, because constructors are similar to functions in this way)

void f(vector<string>); // #1
void f(reference_wrapper<const vector<string>>); // #2

vector<string> data;
const vector<string>& get_data();

f(data); // #1
f(std::cref(data)) // #2

f(get_data()); // #1
f(std::cref(get_data())) // #2

The question is why the vector<string> has the priority here. I looked at Best viable function section here , but it didn't make much sense. It seems, that

4) or, if not that, F1 is a non-template function while F2 is a template specialization

part chooses vector<string> over reference_wrapper<vector<string>> (because reference_wrapper constructor is templated), but I'm not sure, because I can't fully understand if they are equal using the rule

1) There is at least one argument of F1 whose implicit conversion is better than the corresponding implicit conversion for that argument of F2

Can someone please describe all the implicit conversions applied in each case and show the true reason why one overload is preferred over another? To me, they are as follows:

f(data) = f(vector<string>&) -> (*exact match* implicit conversion) -> f(vector<string>)

f(data) = f(vector<string>&) -> (*conversion* implicit conversion) -> f(reference_wrapper<vector<string>>)

Did I miss something?

Another question, connected to this topic: Ranking of implicit conversion sequences section again,here leaves a question, is T(const T&) considered an Exact match (user-defined conversion of class type to the same class) or Conversion?

like image 577
koleydoscope Avatar asked Nov 30 '25 09:11

koleydoscope


1 Answers

First, Although std::reference_wrapper is part of standard library it is treated as user-defined type.

For example an implicit conversion from std::vector & to const std::vector & is always preferred over an implicit conversion from std::vector& to std::reference_wrapper<vector>. That is because (as per standard) the former one is a standard conversion, but the later is a user-defined conversion. the first one is called standard conversion because it adds a const to your type but the second is treated as converting some type to totally different type.

check this code and see cppreference.com.

Second, I'm trying to guess some good alternative. I see you want either to store a reference to vector OR move/(copy as cheap as possible) or (you could say directly initialize) the data inside your class if it is not already stored (safely) in some variable. Maybe you could consider using move semantics. you can play with code here

using TVar = std::variant<reference_wrapper<const vector<string>>, vector<string>>;

class Config {
private:
    TVar data;
public:
    const vector<string>& get_data() const{
        if (data.index() == 1)
            return get<1>(data);
        else return get<0>(data);
    }
    Config(const vector<string>& it):data(cref(it)){}
    Config(vector<string>&& it):data(move(it)){}
};

Here we have two functions.

  1. One that takes reference to "stored value" (more precisely lvalue). wrapping it in cref so that it causes the reference_wrapper alternative in the variant to be the best overload.
  2. The other does the magic. it is the reference to values that are either written directly (aka pvalues) and values that use the magic std::move function (aka xvalues). if you have never seen this, please reference this respectable Q&A What is move semantics?

catch(...) :), this is it. also notice, you don't need the std::monostate as this is only needed for making the variant default constructible (with no arguments). you can make your class default constructible like this.

    Config(vector<string>&& it = {}):data(move(it)){}
like image 162
Mohannad Samir Avatar answered Dec 01 '25 23:12

Mohannad Samir



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!