0

I want a function to return an optional reference to an object. Idea is to avoid copy, but seems like i should not be using according to this discussion: std::optional specialization for reference types

Lets say I have a map of objects, and I want my function to return a reference to an object, if it is exists in the map. What is the best way?

struct HeavyObj;
std::map<int, HeavyObj> my_map;

std::optional<HeavyObj&> get_obj(int key){   
    if(auto it = my_map.find(key); it != my_map.end()){
        return std::optional<HeavyObj&>{ ? };
    } else {
        return {};
    }

ps: needed for C++17

update, If I use a pointer, I get:



#include <optional>
#include <map>
struct HeavyObj{
    int data;
};
static std::map<int, HeavyObj> my_map;

std::optional<HeavyObj*> get_obj(int key){   
    if(auto it = my_map.find(key); it != my_map.end()){
        return std::optional<HeavyObj*>{&(*it)};
    } else {
        return {};
    }
}

int main(){
    auto test  = get_obj(3);
    return 0;
}

// gcc -std=c++17 -Wall optional.cc -lstdc++
optional.cc: In function ‘std::optional<HeavyObj*> get_obj(int)’:
optional.cc:13:47: error: no matching function for call to ‘std::optional<HeavyObj*>::optional(<brace-enclosed initializer list>)’
   13 |         return std::optional<HeavyObj*>{&(*it)};
      |                                               ^
    ```
mu5e
  • 61
  • 5

2 Answers2

0

As mentioned, you can use the fact that an uninitialized pointer is nullptr to mimic the optional behavior.

However, some people might consider it cleaner to use optional anyway as it is more consistent if you have a similar function returning an optional int for instance.

You can use the std::nullopt when the value is not found and simply return the value if it is found (the compiler will do the cast from HeavyObject* to optional<HeavyObject*> but you can do it manually).

  • `std::optional in this case is an example of bad interface. Logically, there are two kinds of return values - a pointer to a found object and some value that corresponds to a missing object. `std::optional` holds three states (empty optional, null pointer, and non-null pointer), not two. So it's not a cleaner return type, it's just a wrong one. – Evg Jun 20 '23 at 01:22
  • I would agree with you and wouldn't use it that way. But purists might say that a pointer should always be initialized. In any case, I think it's important to show how to use optional but I think your point is important too as in practice it's simply wasting ressources. – UnquoteQuote Jun 20 '23 at 01:28
  • 3
    "an uninitialized pointer is `nullptr`" No, it isn't, it is an uninitialized pointer with an indeterminate value. `int *p;` could point anywhere. `int *p = nullptr;` is initialized to `nullptr`. – Retired Ninja Jun 20 '23 at 02:23
  • 1
    *purists might say that a pointer should always be initialized* - `nullptr` is the purest initialization of a pointer. ;) Pointers can be null by their very idea. – Evg Jun 20 '23 at 14:37
0

Return a HeavyObj*, it is the idiomatic solution. The C++ standard library returns pointers that act as optional references in numerous places. For example:

  • std::get_if(std::variant)
  • std::any_cast<T*>(std::any*)

These are both C++17 functions, so they're by no means some outdated design from the 90s. In your case, we can use raw pointers as follows:

HeavyObj* get_obj(int key){   
    if(auto it = my_map.find(key); it != my_map.end()){
        return std::addressof(*it);
    } else {
        return nullptr;
    }
}

Note: we are using std::addressof in case HeavyObj has an overloaded address-of operator.

Issues with std::optional

std::optional<HeavyObj*> would be a bit problematic, because it is optional in two ways:

  • the std::optional may hold no value, or
  • the HeavyObj* inside may be a nullptr

You only want one layer of optionality, so use HeavyObj*.

If you desperately wanted to avoid raw pointers, then the solution would be:

std::optional<std::reference_wrapper<HeavyObj>> get_obj(int key){   
    if(auto it = my_map.find(key); it != my_map.end()){
        return std::ref(*it);
    } else {
        return {};
    }
}

However, this is not very ergonomic, because the return type is incredibly long.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96