'Uniformly calling -> or . in templated code with C++20 concepts

In template code I want to detect if something is a pointer(this includes smart pointers like std::unique_ptr/std::shared_ptr) or value and then call ->some_func() or .some_func().

I know the name of some_func, but sometimes people pass me pointers, sometimes they pass me values(and this is a fixed requirement, long story, but I can not just ask people to use * at callsite).

So this is what I came up with.

#include <iostream>
#include <optional>
#include <memory>

template<typename T> 
auto& get_dotable(T& t){
    if constexpr (requires{*t;}) {
        return *t;
    } else{ 
        return t;
    }
};

void fn()
{
    auto sps=std::make_shared<std::string>("abc");
    auto csps=std::make_shared<const std::string>("abc");
    auto ups=std::make_unique<std::string>("abc");
    auto os=std::optional<std::string>("abc");
    std::cout << get_dotable(sps).size() << std::endl;
    std::cout << get_dotable(csps).size() << std::endl;
    std::cout << get_dotable(ups).size() << std::endl;
    std::cout << get_dotable(os).size() << std::endl;
}

int main() {
    fn();
}

I am fine with it blindly calling * on std::optional without checking if optional is engaged.

It does not work with temporaries, but I am fine with that since I fear lifetime issues, although it prevents some nice usecases like:

std::cout << get_dotable(ups.get()).size() << std::endl;

Apart from these issues that I'm fine with, is there some problem with this solution, and is there a better way to do it instead?



Solution 1:[1]

It does not work with temporaries

This can be fixed easily by using forwarding references.


Your attempt differs significantly from your description. In your description, you want to differentiate pointers from "values" (which is somewhat confusing since pointers are values too) while your attempt differentiates things that have unary operator * and those that don't.

Considering that sometimes the intention is to call member function of the passed object and sometimes the intention is to indirect through the passed object to call the member function of the referred object, there is high chance of confusion when the caller intends to call a member function of the class that has the indirection operator. Example: get_dotable(ups).get().

The described premise of treating pointers differently (from non-pointers) doesn't have this problematic confusion because pointers don't have members. It can probably be implemented with concepts as well, but overloads work too:

template<typename T> 
T&& get_dotable(T&& t){
    return t;
};
template<typename T> 
T& get_dotable(T* t){
    return *t;
};

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1