Search code examples
c++constexprtemplate-meta-programmingstdoptional

Bind reference to T& in both cases, std::optional<T> and T, at compile time


I have a function which takes a generic argument that can either be a std::optional<T> or T directly. For the further context of the function I don't care if the argument came in as an optional, I want to just be able to use a reference to the underlying value T&, which in case of the optional would mean I would have to conditionally extract the value using std::optional::value(). Can this be done generically? I thought I might be able to use an std::reference_wrapper to convert a constexpr conditional back to a reference, but it doesn't work. This is what I've got:

Demo:

#include <cstdio>
#include <optional>
#include <utility>
#include <iostream>

template <typename, template <typename...> class>
struct is_specialization_of : std::false_type {};

template <template <typename...> class Template, typename... Args >
struct is_specialization_of<Template<Args...>, Template> : std::true_type {};

template <typename T, template <typename...> class Template>
concept specialization_of = is_specialization_of<T, Template>::value;


int main() {
    std::optional<int> myopt = 2;

    const auto& ref = [&]{
            if constexpr (specialization_of<decltype(myopt), std::optional>) {
                return std::cref(myopt.value());
            } else {
                return std::cref(myopt);
            }
        }();

    std::cout << ref << std::cout;
}

Yields:

<source>:27:22: error: no match for 'operator<<'

... of sorts and more. Why do I want this? Because I don't want to adapt every passage in the code to either adapt to T's or std::optional<T>'s interface because the verbosity of that would be rampant...

Note: I also tried the non-autodeduced reference: const int& ref = ... but with the same result.


Solution

  • No need to use std::reference_wrapper here, simply declare your lambda to return a reference:

    const auto& ref = [&]() -> auto& {
                if constexpr (specialization_of<decltype(myopt), std::optional>) {
                    return myopt.value();
                } else {
                    return myopt;
                }
            }();
    

    Furthermore I would advise you to not use a fancy specialization_of template in favor of simple function overloading:

    template <typename T>
    T const& extract_value(T const& t) { return t; }
    
    template <typename T>
    T const& extract_value(std::optional<T> const& t) { 
        return extract_value(t.value()); // Recurse to handle the case where T is std::optional<U>
    }  
    
    int main() {
        std::optional<int> myopt = 2;
        const auto& ref = extract_value(myopt);
        std::cout << ref << std::endl;
    }