编写一个函数,接受一个可选项并将其转换为非可选项?

47

我正在尝试以单子风格的方式,对 std::optional 编写语法糖。请考虑以下内容:

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

当前函数只接受可选的T1(例如int),即使存在从Tstd::optional<T>2的转换。请问是否有一种方法可以使f接受std::optional<T>或者在调用时将T转换为可选类型,而不需要定义一个重载函数3

1) f(0)会报错:error: no matching function for call to 'f(int)'note: template argument deduction/substitution failed,(演示)。
2) 因为模板参数推导不考虑转换。
3) 对于一元函数,重载是可接受的解决方案,但对于二元函数,如operator+(optional, optional),则开始变得烦人,并且对于三元、四元等函数则更加困难。

13
如果你使用的是C++17,可以使用constexpr if非常容易地编写代码来实现将一个变量加上optional类型的功能。具体步骤是先给该变量加上模板参数T,然后检查它是否已经是optional类型,如果不是,就将其转换为optional类型。请注意,不要改变原始含义,并尽可能使翻译更通俗易懂。 - xaxxon
8
@xaxxon 值得回答。 - YSC
3
你能澄清一下你所说的“以单子风格”的意思吗?因为按照你的问题,似乎你对某种自动转换或重载感兴趣,而两个答案都是沿着这些方向做的。但这与单子完全没有任何关系 - Konrad Rudolph
@KonradRudolph 提到单子只是为了给出一个上下文。我想要的是,假设 F=λxy.,有一个 optional(x) 和一个 optional(y),返回一个 optional(F(xy))。为了让这样的定义有用并允许其用户链接表达式,它需要接受可选项和标量。 - YSC
这个定义的问题在于,与单子不同,它无法组合。你的函数如何处理std::optional<std::optional<T>>?而如果你有一个单子提升运算符,你只需要lift(F),就不会有这个问题了。 - Konrad Rudolph
显示剩余3条评论
4个回答

39

另一个版本。这个版本没有涉及任何内容:

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

类模板参数推导在这里已经做得很好了。如果 t 是一个 optional,复制推导候选项将被优先选择,并且我们会得到相同的类型返回值。否则,我们进行包装。


3
尽管这不是代码高尔夫比赛,但我有种感觉这个答案应该被选为最佳答案:D - YSC
1
很好的思路;不过,这里难道不需要将opt定义为std::optional<T>吗? - LThode
7
非常不行。 - Barry
3
@Barry -- 啊哈,我现在明白你的意思了--我以前从没遇到过类模板参数推导! - LThode
1
这个缺少重载决议功能;有没有一种方法可以测试std::optional opt = std::forward<T>(t)是否能够编译? - Yakk - Adam Nevraumont
显示剩余2条评论

15

将可选项作为参数改为使用免赔额模板参数:

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

现场示例


14

这使用了我最喜欢的类型特征之一,它可以检查任何全类型模板是否与某个类型相匹配。

#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


你的trait非常好。你知道为什么需要std::decay吗? - YSC
2
@YSC 如果没有 std::decay,它将无法自动处理像 std::optional<int>& 和相关类型。 - bartop

5

另一种版本。这个版本不涉及编写特征:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接