Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deducing a const l-value reference from a non-const l-value reference in C++ template

Suppose you have the following pair of functions:

void f(const int&) { 
    // Do something, making a copy of the argument.
}

void f(int&&) { 
    // Do the same thing, but moving the argument.
}

They are fairly redundant—the only difference between the functions being whether they copy or move their argument. Of course, we can do better by re-writing this as a single template function:

template<typename T> 
void g(T&&) { 
    // Do something, possibly using std::forward to copy or move the argument.
}

This works, and is a commonly used idiom in practice. But the template might be instantiated into three functions, up from our two above. We can verify this occurs with the following piece of code:

#include <iostream>

template<typename T> constexpr char *type = nullptr;
template<> constexpr const char *type<int&> = "int&";
template<> constexpr const char *type<const int&> = "const int&";
template<> constexpr const char *type<int> = "int";

template<typename T> 
void g(T&&) { 
    std::cout << reinterpret_cast<void*>(&g<T>)
              << " = &g<" << type<T> << ">" << std::endl;
}

int main() {
    int i = 0;
    const int& cr = 0;

    g(i);
    g(cr);
    g(0);

    return 0;
}

/*
Prints:

0x100f45080 = &g<int&>
0x100f45100 = &g<const int&>
0x100f45180 = &g<int>
*/

This has added a third function for the case when T = int&, which we didn't have when we were using our non-templated function f above. In this case, we don't actually need this non-const l-value reference version of the function—given f was sufficient for our original needs—and this increases the size of our code, especially if we have many template functions written this way that call each other.

Is there a way to write our function g above so that the compiler will automatically deduce T = const int& when g(i) is called in our example code? I.e., a way where we don't have to manually write g<const int&>(i) yet still get the desired behavior.

like image 814
Paul Merrill Avatar asked Oct 17 '22 05:10

Paul Merrill


1 Answers

It is a subjective point-of-view to say "forward references" ("universal references") are better than dedicated overloads. There are certainly many cases where this is true, but if you want to have full control they won't do all the jobs.

You could explicitly make sure users do not pass non-const lvalue references, by adding

    static_assert(!std::is_lvalue_reference<T>::value || std::is_const<typename std::remove_reference<T>::type>::value, "only call g with const argument");

inside g, but this is not in all cases a very good solution.

Or you do what is done for vector::push_back(...) and provide explicit overloads -- but this was your starting point, see https://en.cppreference.com/w/cpp/container/vector/push_back.

The 'correct' answer just depends on your requirements.

Edit: the suggestion of @Sjoerd would look something like:

template <typename T>
class aBitComplicated {
public:
 void func(T&& v) { internal_func(std::forward<T>(v)); }
 void func(const T& v) { internal_func(v); }
private:
 template <typename U>
 void internal_func(U&& v) { /* your universal code*/ }
};

There also a bit more sophisticated/complicated version of this, but this here should be the most simple version to achieve what you asked for.

like image 136
Ralf Ulrich Avatar answered Oct 21 '22 00:10

Ralf Ulrich