C++ SFINAE to choose an overload between reference type and argument converted to reference?

Consider:

struct BigData {...};

// Let's get a BigData by reference, but use it as a value.
// For example, we may want to make a copy of the object, but we'd
// like to avoid the pass-by-value overhead at the call site.
template <typename T, *some template magic?*>
void processData(T& t) {
    printf("Your BigData was converted to a reference argument.\n");
    ...
}

// Now, we want an overload that will know that there wasn't a
// conversion to reference and will treat it like a reference.
// Perhaps we are adding this BigData to a list of references.
template <typename T, *some template magic?*>
void processData(T& t) {
    printf("You gave me a BigData reference.\n");
    ...
}

int main() {
    BigData data;
    BigData& ref = data;

    processData(data); // "Your BigData was converted to a reference argument."
    processData(ref); // "You gave me a BigData reference."

    return 0;
}

In short, my objective is to have overloads that distinguish between where a reference binding came from - either a value of the type or an (already) reference type. I have tried using std::enable_if and family in combination with overloads that do and do not take references to T, but I can't find a way to accomplish this. Any help is greatly appreciated!

Upvotes: 0

Views: 323

Answers (1)

Curious
Curious

Reputation: 21510

I think you are not understanding how reference binding works. You can not only bind a reference to another reference but also a value that is of value type (i.e. not reference qualified)

So if you want to pass the BigData object by reference, you just need to do the following

template <typename T>
void processData(T& t) {
    cout << "Your BigData was passed as a reference argument" << endl;
    // ...
}

int main() {
    BigData data;
    BigData& ref = data;

    processData(data); 
    processData(ref);

    return 0;
}

Here both the processData calls will pass the BigData object by reference (i.e. no copies during function invocation).

You don't need to deal with the case where the object being passed to the function is a reference separately from the normal case. std::enable_if is not required here.

Further there is no way to differentiate between the two cases, either using data or ref as arguments to functions because they are both lvalues. You will have to do something like the following, if you want to inspect whether the decltype of the expression is a reference or not

#include <iostream>
#include <type_traits>

using std::cout;
using std::endl;

template <typename T, std::enable_if_t<std::is_reference<T>::value>* = nullptr>
void processData(std::add_lvalue_reference_t<T>) {
    cout << "You gave me a reference argument." << endl;
}

template <typename T, std::enable_if_t<!std::is_reference<T>::value>* = nullptr>
void processData(std::add_lvalue_reference_t<T>) {
    cout << "Your argument was converted to a reference." << endl;
}

int main() {
    auto integer_value = 1;
    const auto& integer_ref = 2;
    processData<decltype(integer_value)>(integer_value);
    processData<decltype(integer_ref)>(integer_ref);

    return 0;
}

Upvotes: 3

Related Questions