web-dev-qa-db-fra.com

Faire une fonction acceptant une option pour accepter une non-option?

J'essaie d'écrire du sucre syntaxique, dans un style monade, sur std::optional. Veuillez considérer:

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

En l'état, cette fonction ne peut pas être appelée avec un T non facultatif1 (par exemple un int), même s’il existe une conversion de T en std::optional<T>2.

Est-il possible de faire accepter par f un std::optional<T> Ou un T (converti en optionnel sur le site de l'appelant), sans définir une surcharge 3?


1) f(0): error: no matching function for call to 'f(int)' et note: template argument deduction/substitution failed, ( démo ).
2) Parce que la déduction d'argument de modèle ne prend pas en compte les conversions.
3) La surcharge est une solution acceptable pour une fonction unaire, mais commence à être gênante lorsque vous avez des fonctions binaires comme operator+(optional, optional), et elle est pénible pour ternaire, 4-aires, etc. fonctions.

46
YSC

Une autre version. Celui-ci n'implique rien:

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

La déduction d'argument de modèle de classe fait déjà la bonne chose ici. Si t est un optional, le candidat à la déduction pour copie sera privilégié et nous aurons le même type. Sinon, nous l'enveloppons.

37
Barry

Au lieu de prendre optionnel comme argument, prenez le paramètre de modèle déductible:

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));
}

Exemple en direct

15
bartop

Cela utilise l'un de mes traits de type préférés, qui peut comparer n'importe quel modèle de type avec un type pour voir si c'est le modèle pour celui-ci.

#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

Une autre version. Celui-ci n'implique pas d'écrire des traits:

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