web-dev-qa-db-de.com

Machen Sie eine Funktion, die ein optionales akzeptiert, um ein nicht optionales zu akzeptieren?

Ich versuche syntaktischen Zucker im Monadenstil über std::optional Zu schreiben. Beachten Sie bitte:

template<class T>
void f(std::optional<T>)
{}

Wie es ist, kann diese Funktion nicht mit einem nicht optionalen T aufgerufen werden1 (z. B. ein int), obwohl eine Konvertierung von T in std::optional<T> vorhanden ist2.

Gibt es eine Möglichkeit, f dazu zu bringen, einen std::optional<T> Oder einen T (der auf der Anruferseite in einen optionalen konvertiert wurde) zu akzeptieren, ohne dies zu definieren? eine Überlastung 3?


1) f(0): error: no matching function for call to 'f(int)' und note: template argument deduction/substitution failed, ( demo ).
2) Da der Abzug von Vorlagenargumenten keine Konvertierungen berücksichtigt.
3) Überladen ist eine akzeptable Lösung für eine unäre Funktion, wird jedoch zu einem Ärgernis, wenn Sie Binärfunktionen wie operator+(optional, optional) haben, und ist ein Schmerz für ternäre, 4-ary, etc. Funktionen.

46
YSC

Andere Version. Dieser beinhaltet nichts:

template <typename T>
void f(T&& t) {
    std::optional opt = std::forward<T>(t);
}

Der Abzug von Argumenten für Klassenvorlagen macht hier bereits das Richtige. Wenn t ein optional ist, wird der Kopienabzugskandidat bevorzugt und wir erhalten den gleichen Typ zurück. Ansonsten wickeln wir es ein.

37
Barry

Anstatt optional als Argument zu verwenden, verwenden Sie den abziehbaren Vorlagenparameter:

template<class T>
struct is_optional : std::false_type{};

template<class T>
struct is_optional<std::optional<T>> : std::true_type{};

template<class T, class = std::enable_if_t<is_optional<std::decay_t<T>>::value>>
constexpr decltype(auto) to_optional(T &&val){
    return std::forward<T>(val);
}

template<class T, class = std::enable_if_t<!is_optional<std::decay_t<T>>::value>>
constexpr std::optional<std::decay_t<T>> to_optional(T &&val){
    return { std::forward<T>(val) };
}

template<class T>
void f(T &&t){
    auto opt = to_optional(std::forward<T>(t));
}

int main() {
    f(1);
    f(std::optional<int>(1));
}

Live-Beispiel

15
bartop

Dabei wird eines meiner bevorzugten Typmerkmale verwendet, mit dem jede Vorlage für alle Typen mit einem Typ verglichen werden kann, um festzustellen, ob es sich um die Vorlage für diesen Typ handelt.

#include <iostream>
#include <type_traits>
#include <optional>


template<template<class...> class tmpl, typename T>
struct x_is_template_for : public std::false_type {};

template<template<class...> class tmpl, class... Args>
struct x_is_template_for<tmpl, tmpl<Args...>> : public std::true_type {};

template<template<class...> class tmpl, typename... Ts>
using is_template_for = std::conjunction<x_is_template_for<tmpl, std::decay_t<Ts>>...>;

template<template<class...> class tmpl, typename... Ts>
constexpr bool is_template_for_v = is_template_for<tmpl, Ts...>::value;


template <typename T>
void f(T && t) {
    auto optional_t = [&]{
        if constexpr (is_template_for_v<std::optional, T>) {
            return t; 
        } else {
            return std::optional<std::remove_reference_t<T>>(std::forward<T>(t));
        }
    }();
    (void)optional_t;
}

int main() {
    int i = 5;
    std::optional<int> oi{5};

    f(i);
    f(oi);
}

https://godbolt.org/z/HXgoEE

13
xaxxon

Andere Version. Dieser beinhaltet nicht das Schreiben von Merkmalen:

template <typename T>
struct make_optional_t {
    template <typename U>
    auto operator()(U&& u) const {
        return std::optional<T>(std::forward<U>(u));
    }
};

template <typename T>
struct make_optional_t<std::optional<T>> {
    template <typename U>
    auto operator()(U&& u) const {
        return std::forward<U>(u);
    }
};

template <typename T>
inline make_optional_t<std::decay_t<T>> make_optional;

template <typename T>
void f(T&& t){
    auto opt = make_optional<T>(std::forward<T>(t));
}
5
Barry