0

I'm trying to figure out how to write "fully generic function" for sqr operation (it's actually can be multiply, division, add, does not really matter).

Consider the following code

#include <iostream>

struct A
{
    int val = 2;

    A() = default;
    A(const A&) = delete; // To make sure we do not copy anything
    A(A&& a) = delete; // To make sure we do not move anything
    auto operator=(auto) = delete; // To make sure we do not assign anything
    // This is important part, we do not want to create a new object on each multiplication.
    // We want just to update the old one.
    A& operator*(const A& a) 
    {
        val *= a.val;
        return *this;
    }
};

// Just for easy printing (you can ignore it).
std::ostream &operator<<(std::ostream &os, const A& a) { 
    return os << a.val;
}

// Here auto&& represents forwarding reference and should automatically understand whether input r or l value.
auto&& sqr(auto&& val)
{
    return val * val;
}

int main()
{
    A a;
    std::cout << sqr(a) << "\n"; // OK
    std::cout << sqr(A()) << "\n"; // OK

    std::cout << sqr(1) << "\n"; // Wrong, ref to local returned
    int i = 2;
    std::cout << sqr(i) << "\n"; // Wrong, ref to local returned
}

sqr function here is meant to be sort of generic stuff, it should handle all possible situations (r-values, l-values) and for object a it's actually does, but not for i. I can't get why it's trying to return reference instead of copy. Could anyone please shed some light on the situation? Is there any way I can accomplish this task easily (with one template function ideally)? I can use c++ 20 standard if necessary.

Dmitry
  • 1,065
  • 7
  • 15
  • A typical implementation of `operator*`, as well as the built-in operator, produce a temporary. `sqr` then returns a reference to that temporary, which is destroyed by the time the function returns. Any attempt to use that reference then exhibits undefined behavior. `sqr` only works with `A` because `A` provides a highly unusual implementation of `operator*` – Igor Tandetnik Nov 15 '20 at 20:08
  • It never calls A's constructor inside the function, so temporary object was never created, how can it create reference to unexisting object? – Dmitry Nov 15 '20 at 20:08
  • This doesn’t address the question, but `A::operator*` should be named `operator*=` because it modifies the object that it’s called on. – Pete Becker Nov 15 '20 at 20:36

1 Answers1

4
auto&& sqr(auto&& val)
{
    return val * val;
}

sqr above always returns a reference. But returning a reference to a local is always wrong. Let the return-type deduce to a non-reference by using auto instead.

constexpr // May be constexpr for some types
auto sqr(auto&& x) // return type is non-reference or trailing
noexcept(noexcept(x*x)) // propagate noexcept
-> decltype(x*x) // enable SFINAE
{ return x * x; }
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • This looks very familiar .. see [`std::multiplies`](http://eel.is/c++draft/arithmetic.operations.multiplies) – Marshall Clow Nov 15 '20 at 20:35
  • It works like a charm, I actually got the point. One thing not really clear to me. Is there any difference between ```decltype(auto)``` and ```decltype(x * x)``` according to this answer https://stackoverflow.com/questions/24109737/what-are-some-uses-of-decltypeauto it i probably better to use ```decltype(auto)``` – Dmitry Nov 15 '20 at 20:44
  • `decltype(auto)` will be deduced from the initializing expression, and might be a reference. `auto` the same, but will never be a reference. `decltype(x*x)` is a concrete type, and not a reference, `decltype((x*x))` is a concrete type, and could be a reference. Anyway, the post you linked doesn't say `decltype(auto)` is better, but that infinite regression is to be avoided. How the post linked in the accepted answer triggered it in the declaration is interesting. – Deduplicator Nov 15 '20 at 21:15
  • `decltype(x*x)` and `decltype((x*x))` are always the same. – HolyBlackCat Dec 28 '22 at 11:30