Search code examples
c++templatesrvalue-reference

A templated function to always retrieve the value of T, being T an universal reference


I want to have a helper function that helps me to get the value or the value of the pointee when its a pointer.

For example, I want to substract a pair numerical values or the values for the types that defines an overload for the - operator.

This is my code:

template <typename T>
T value_retriever(const T&& val) {
    if constexpr (std::is_pointer_v<decltype(val)> || std::is_reference_v<decltype(val)>)
        return *val;
    else
        return std::forward<T>(val);
}


template <typename T>
[[nodiscard]] constexpr auto minus(const T&& lhs, const T&& rhs) -> T {
    return value_retriever(std::move(lhs)) - value_retriever(std::move(rhs));
}

And this are the errors that I am facing:

In module 'math' imported from zero\main.cpp:8:
In module 'math.ops' imported from zero/ifc/math/math.cppm:3:
zero/ifc/math/ops/arithmetic.cppm:25:16: error: cannot initialize return
      object of type 'const int *' with an rvalue of type 'long long'
        return value_retriever(std::forward<T>(lhs)) - value_retriever(std::forward<T>(rhs));
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
zero\main.cpp:38:83: note: in instantiation of function template
      specialization 'zero::math::minus<const int *>' requested here
    std::cout << "Subtraction of two integer l-value references: " << zero::math::minus(&a, &b) << "\n\n";
                                                                                  ^
In module 'math' imported from zero\main.cpp:8:
In module 'math.ops' imported from zero/ifc/math/math.cppm:3:
zero/ifc/math/ops/arithmetic.cppm:9:20: error: indirection requires pointer
      operand ('int' invalid)
            return *val;
                   ^~~~
zero/ifc/math/ops/arithmetic.cppm:25:16: note: in instantiation of function
      template specialization 'zero::math::value_retriever<int>' requested here
        return value_retriever(std::forward<T>(lhs)) - value_retriever(std::forward<T>(rhs));
               ^
zero/ifc/math/ops/arithmetic.cppm:9:20: error: cannot initialize return
      object of type 'const int *' with an lvalue of type 'const int'
            return *val;
                   ^~~~
zero/ifc/math/ops/arithmetic.cppm:25:16: note: in instantiation of function
      template specialization 'zero::math::value_retriever<const int *>' requested here
        return value_retriever(std::forward<T>(lhs)) - value_retriever(std::forward<T>(rhs));
               ^
3 errors generated.

The call site:

const int a = 16;
const int b = 20;

std::cout << "Subtraction of two integers, passed as l-value references: " << zero::math::minus(&a, &b) << "\n\n";

I am failing to understand certain things about universal references.That's clear. But the error that is making me mad is this:

indirection requires pointer
      operand ('int' invalid)
            return *val;
  • How's this possible? If the type traits used in the if constexpr branch detected that val is a ptr or a reference, how can I have an error that says that `indirection requires ptr operand?
  • How can improve the public API to work correctly with both values and/or pointers and references? So I am able to return a value T?

Solution

  • Supposed you want to instantiate the function template for int then this branch makes no sense:

    if constexpr (std::is_pointer_v<decltype(val)> || std::is_reference_v<decltype(val)>)
        return *val;
    

    A reference to an int has no operator*.

    The forwarding reference is a red herring. The error is not related to using a forwarding reference, and you do not need a forwarding reference when dealing with either int or int*.

    Here is how you can do it:

    #include <iostream>
    
    template <typename T>
    auto value_retriever(T& val) {
        if constexpr (std::is_pointer_v<T>)
            return *val;
        else
            return val;
    }
    
    template <typename T>
    [[nodiscard]] constexpr auto minus(const T& lhs, const T& rhs) -> std::remove_pointer_t<T> {
        std::cout << value_retriever(lhs) << " - " << value_retriever(rhs) << "\n";
        return value_retriever(lhs) - value_retriever(rhs);
    }
    
    int main() {
        int x = 42;
        int y = 40;
        std::cout << minus(&x,&y) << "\n";
        std::cout << minus(x,y) << "\n";
    }
    

    Live Demo