首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C++ Maybe<T>实现

C++ Maybe<T>实现
EN

Code Review用户
提问于 2019-01-15 15:14:27
回答 2查看 2.8K关注 0票数 7

为了提高我对C++模板元编程、SFINAE、参考和总体类设计的理解,我尝试在C++中实现一个Maybe类。

当然,这个类在很大程度上是基于Haskell的Maybe Monad,并且具有相同的功能。我知道std::optional在C++17中几乎做了同样的事情,但我决定不按照标准所指定的那样实现它。

具体来说,我重命名了几个函数,并添加了我自己的一些函数(即apply方法)。更值得注意的是,我试图让它支持可能的引用(通过使用std::reference_wrapper)。

以下代码如下:

maybe.hpp:

代码语言:javascript
复制
#pragma once

#include 
#include 
#include 
#include 

namespace maybe {

struct nothing_t {} nothing;

template 
class Maybe_Base {

public:

    struct bad_access : std::exception {
        virtual const char* what() const noexcept {
            return "Attempted to access a value of a Maybe wrapper that doesn't exist.";
        }
    };


    Maybe_Base():
        val(nullptr)
    {}

    Maybe_Base(nothing_t):
        val(nullptr)
    {}

    template 
    explicit Maybe_Base(Args&&... args) {
        val = new T(std::forward(args)...);
    }

    ~Maybe_Base() {
        if (val)
            val->~T();
        delete val;
    }

    // Is there a way to ensure extraneous copies aren't made? Or is the only option just to delete the copy constructor entirely?
    Maybe_Base(const Maybe_Base& other) {
        if (other.val)
            val = new T(*other.val);
        else
            val = nullptr;
    }

    Maybe_Base& operator=(Maybe_Base other) {
        swap(*this, other);
        return *this;
    }

    friend void swap(Maybe_Base& a, Maybe_Base& b) {
        using std::swap;
        swap(a.val, b.val);
    }

    Maybe_Base(Maybe_Base&& other) {
        this->val = other.val;
        other.val = nullptr;
    }


    inline bool empty() const {
        return val == nullptr;
    }

    inline bool hasValue() const {
        return !empty();
    }

    inline explicit operator bool() const {
        return hasValue();
    }


    T value() {
        if (empty())
            throw bad_access();
        else
            return *val;
    }

    T valueOr(T defaultVal) {
        if (empty())
            return defaultVal;
        return *val;
    }

    const T& operator*() const {
        return *val;
    }
    T& operator*() {
        return *val;
    }

    const T* operator->() const {
        return val;
    }
    T* operator->() {
        return val;
    }


    void clear() {
        val->~T();
        delete val;

        val = nullptr;
    }

    template 
    std::enable_if_t<    !std::is_void_v>
                      && !std::is_member_function_pointer_v,
        Maybe_Base>
    >
    apply(Func f, Args&&... args) {
        if (*this)
            return Maybe_Base>{f(this->value(), std::forward(args)...)};
        return nothing;
    }

    template 
    std::enable_if_t<     std::is_void_v>
                      && !std::is_member_function_pointer_v,
        void
    >
    apply(Func f, Args&&... args) {
        if (*this)
            f(this->value(), std::forward(args)...);
    }

    template 
    std::enable_if_t<    !std::is_void_v>
                      &&  std::is_member_function_pointer_v,
        Maybe_Base>
    >
    apply(Pointer_To_Method f, Args&&... args) {
        if (*this)
            return Maybe_Base>{(this->value().*f)(std::forward(args)...)};
        return nothing;
    }

    template 
    std::enable_if_t<     std::is_void_v>
                      &&  std::is_member_function_pointer_v,
        void
    >
    apply(Pointer_To_Method f, Args&&... args) {
        if (*this)
            (this->value().*f)(std::forward(args)...);
    }

private:
    T* val;
};

template >
class Maybe;

template 
class Maybe : public Maybe_Base {

public:
    template 
    Maybe(Args&&... args): Maybe_Base(std::forward(args)...) {}
};

template 
class Maybe : public Maybe_Base>> {
    typedef std::reference_wrapper> Wrap_Type;
public:
    template 
    Maybe(Args&&... args): Maybe_Base(std::ref(args)...) {}

    T value() {
        return Maybe_Base::value().get();
    }

};

} // namespace maybe

下面是一个小的测试.cpp文件,以确保实现工作正常:

代码语言:javascript
复制
#include 
#include 
#include 

#include "maybe.hpp"

struct foo {
    int i;

    foo(int i_): i(i_) {}
    foo() {}

    void bar(int j) {
        std::cout << "Bar." << ' ' << i*j << std::endl;
    }

    ~foo() {
        std::cout << "Deleting a foo..." << std::endl;
    }
};

maybe::Maybe letter(int i) {
    if (i >= 0 && i < 26)
        return maybe::Maybe(i + 'A');
    return maybe::nothing;
}

int main() {

    maybe::Maybe> i{ std::make_unique(8) };

    maybe::Maybe> j = std::move(i);

    maybe::Maybe f{12321};

    std::cout << std::boolalpha << j.hasValue() << ' ' << i.hasValue() << std::endl;

    f.apply(&foo::bar, 2);



    int p = 7;
    maybe::Maybe q = p;



    std::cout << q.value() << std::endl;

    p = 8;

    std::cout << q.value() << std::endl;

    // Could I possibly do something simpler like `q = 6`?
    q.value() = 6;

    std::cout << p << std::endl;

    q.apply([] (int a) { std::cout << a << std::endl; });
    std::cout << q.apply([] (int a) { return a + 1; }).value();


    maybe::Maybe c = letter(12);
    std::cout << c.value() << std::endl;
}

有什么事情我好像搞砸了,或者我忽略了什么情况?我特别关心的是确保实现是高效的,而且使用起来很容易。

EN

回答 2

Code Review用户

回答已采纳

发布于 2019-01-15 20:33:42

  • 使用std::unique_ptr。(它比手动内存管理更安全、更容易)。
  • bug:delete操作符将调用对象析构函数。我们不应该手动调用析构函数。(这是一个非常好的例子,说明了为什么我们应该使用std::unique_ptr并完全避免这一点:D )。
  • 请注意,std::optional没有分配内存本身,而是将包含的对象保存在堆栈中。这可以用布尔标志和std::aligned_storage完成,也可以用std::variant完成。
  • valuevalueOr可以是const函数。value也许应该返回一个引用,并具有const和non版本。
  • 意见:我真的不喜欢std::optional重载operator->operator*。它们是没有必要的,而且不太明显它是什么类型。它不是指针类型(至少在语义上是这样),所以我认为它们没有意义。就我个人而言我会跳过它们。
  • 如果我们要为引用类型创建一个特例,并且在内部隐藏std::reference_wrapper,那么我们需要重新实现其他访问函数,而不仅仅是value()。目前,reference_wrapper是通过valueOroperator*operator->公开的。(这导致了“引用可能”的不同实现,我不知道这是否是一件好事。但是,我觉得我们要么完全隐藏std::reference_wrapper,要么让用户自己创建一个Maybe> (如果他们需要的话)。
  • apply方法非常有趣。::)
    • 我认为我们需要const版本(用于调用const Maybe‘S中包含的类型的const成员函数)。
    • 由于Maybe也可以存储简单的apply类型,其中D44函数没有意义,也许apply应该实现为一组空闲函数(并命名为invoke_maybe或类似的函数)。这将提供一个更符合std::invokestd::bind的调用语法。它还允许为其他类实现invoke_maybe,如std::function (或std::optional),必要时返回一个空的Maybe
    • 特性请求:请注意,std::invoke允许我们访问成员变量,而不仅仅是成员函数。目前的apply实现似乎不支持这一点,但会非常酷。
票数 5
EN

Code Review用户

发布于 2019-01-16 16:54:04

坦率地说,我不确定它是否真的是一个Maybe实现。实际上,它与智能指针并没有太大的不同(嗯,也许没那么聪明,因为析构函数删除了底层值两次,如@ very 673679所指出的那样:)。顺便说一句,我不认为智能指针-or任何指针-是Haskell Maybe类型的一个糟糕的近似:它可以是nullptr (Nothing)或指向某个值(Just some_value)。当然,std::optional可能会更有效,因为它将可选值存储在堆栈上,但我不认为这是朝向Maybe的概念飞跃:一方面指针和std::optional的真正区别是Maybe是求和类型,它可以是不同类型的类型之一,而指针或std::optional是定义良好的空/空值类型。

在C++中实现和类型是相当困难的。标准库的sum类型- -std::variant- -相当麻烦,并且吸引了令人信服的投诉。但是,它也是一条获得比指针或可选指针更强大的途径:它们可以是Maybe的一个很好的近似,但不是Either,这一点根本上没有区别,而且更强大。

那么,Maybe作为一个sum类型在C++中会是什么样子?我想说的是:

代码语言:javascript
复制
#include 

struct Nothing {};

template 
struct Just {
    Just(const T& t) : value(t) {}
    Just(T&& t) : value(t) {}
    Just() = default;

    T value;   
};

template 
using Maybe = std::variant>;

那你会怎么用呢?创建一个很像你所做的:

代码语言:javascript
复制
Maybe letter(int i) {
    if (i >= 0 && i < 26) return Just('A' + i);
    return Nothing();
}

现在,您能用它做什么呢?Maybe是Haskell意义上的函子,所以您需要一种将函数映射到它上的方法。Haskell的签名是:(a -> b) -> F a -> F b。C++实现将遵循以下思路:

代码语言:javascript
复制
template 
auto fmap(Fn fn, const Maybe& mb) {

    using return_type   = decltype(fn(std::declval()));    

    auto visitor = [fn](auto&& arg) -> Maybe 
    {
        using Type = std::decay_t;
        if constexpr(std::is_same_v) return Nothing();
        else return Just(fn(arg.value));
    };

    return std::visit(visitor, mb); 
      /* visit is the *apply* you're looking for: given a visitor with 
         overloads for any type the variant can contain a value of, it 
         will apply the correct overload on the value it contains */
}

现在也许也是一首单曲。如果您想实现>>= (又名bind),它在Haskell中的签名是(a -> M b) -> M a -> M b,那么它并没有什么不同:

代码语言:javascript
复制
template 
auto bind(Fn fn, const Maybe& mb) {

    using return_type = decltype(fn(std::declval()));    

    auto visitor = [fn](auto&& arg) -> return_type 
    {
        using Type = std::decay_t;
        if constexpr(std::is_same_v) return Nothing();
        else return fn(arg.value);
    };

    return std::visit(visitor, mb);
}

下面是一些代码片段的链接,如果您想探索这条静脉的话。

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/211558

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档