Majid Azimi
Majid Azimi

Reputation: 5745

Template type deduction warns returning reference to local temporary object

Consider these thee max function:

template<typename T>
const T& max(const T& a, const T& b) {
    return b < a ? a : b;
}

// overload max(a, b) for char* arguments 
const char* max(const char* a, const char* b) {
    return std::strcmp(b, a) < 0 ? a : b;
}

template<typename T>
const T& max(const T& a, const T& b, const T& c) {
    return max(max(a,b), c);
}

Now, when calling three-argument max, it returns correct result. However, it also shows "Returning reference to local temporary object" during compilation.

int main() {
    const char* a = "aaa";
    const char* b = "bbb";
    const char* c = "ccc";

    auto result = max(a, b, c);
    // this correctly prints "ccc"
    std::cout << result << std::endl; 

    return 0;
}

Would you please step by step explain how types are deduced in this example which leads to returning local reference to temporary object?

Upvotes: 0

Views: 64

Answers (2)

user10605163
user10605163

Reputation:

In main:

auto result = max(a, b, c); calls the third overload with T = const char* because it is the only viable one (three arguments).

In that function:

max(a,b) calls the second overload: For the first overload T = const char* is chosen by template argument deduction. Then all arguments in overload one and two are considered exact match for the purpose of function overloading. This is because for the first overload no conversion is required and for the second overload only a lvalue-to-rvalue conversion is required. Therefore none of the two overloads is preferred due to the conversion sequence (none of the additional ordering rules of conversion sequences applies either) and instead overload two is chosen because non-template functions are preferred over template functions.

For the outer call max(max(a,b),c) the same reasoning as above applies, only that the first argument is a rvalue expression of type const char*, making the lvalue-to-rvalue conversion for the second overload unnecessary and for the first overload reference binding is considered exact match as well. Again none of the additional conversion sequence ordering rules apply.

Therefore the return value of the latter expression is a temporary const char* which will be bound to the reference returned by the third overload.

Upvotes: 3

xaxxon
xaxxon

Reputation: 19771

You return a char * by value from your middle 2-param max() to your three parameter max() which then returns that temporary by reference.

Instead, take the char*'s by ref, too. You also need to lose the const's on your 3-way max so that you don't end up with const char * const's. On top of all that, you also need to handle rvalues correctly, which I think the below code at least comes closer to doing. You have to consider an r-value qualified operator<.

#include <string.h>
#include <type_traits>
#include <utility>

struct C {
    bool operator<(C const & c) && {return false;}
};

// overload max(a, b) for char* arguments 
const char*& max(const char*& a, const char*& b) {
    return strcmp(b, a) < 0 ? a : b;
}

template<typename T>
decltype(auto) max(T&& a, T&& b) {
    return std::forward<T>(b) < std::forward<T>(a) ? std::forward<T>(a) : std::forward<T>(b);
}

template<typename T>
decltype(auto) max(T&& a, T&& b, T&& c) {
    return max(max(std::forward<T>(a), std::forward<T>(b)), std::forward<T>(c));
}

int main() {
    const char* a = "aaa";
    const char* b = "bbb";
    const char* c = "ccc";

    auto result = max(a, b, c);
    // this correctly prints "ccc"

    max(1, 2, 3);

    max("a", "b", "c");

    max (C(), C(), C());

    return 0;
}

https://godbolt.org/z/sMo9dx

Upvotes: 1

Related Questions