这是Result
类的定义,该类旨在模拟Haskell中的Either单子逻辑(Left
是Failure
; Right
是{{1} }。
Success
当尝试使用#include <string>
#include <functional>
#include <iostream>
template <typename S, typename F>
class result
{
private:
S succ;
F fail;
bool pick;
public:
/// Chain results of two computations.
template <typename T>
result<T,F> operator&&(result<T,F> _res) {
if (pick == true) {
return _res;
} else {
return failure(fail);
}
}
/// Chain two computations.
template <typename T>
result<T,F> operator>>=(std::function<result<T,F>(S)> func) {
if (pick == true) {
return func(succ);
} else {
return failure(fail);
}
}
/// Create a result that represents success.
static result success(S _succ) {
result res;
res.succ = _succ;
res.pick = true;
return res;
}
/// Create a result that represents failure.
static result failure(F _fail) {
result res;
res.fail = _fail;
res.pick = false;
return res;
}
};
运算符组合两个结果时,一切都很好:
&&
但是,当尝试在结果之上链接计算时,会出现编译错误:
int
main(int argc, char* argv[])
{
// Works!
auto res1 = result<int, std::string>::success(2);
auto res2 = result<int, std::string>::success(3);
auto res3 = res1 && res2;
}
result<int, std::string>
triple(int val)
{
if (val < 100) {
return result<int, std::string>::success(val * 3);
} else {
return result<int, std::string>::failure("can't go over 100!");
}
}
int
main(int argc, char* argv[])
{
// Does not compile!
auto res4 = result<int, std::string>::success(2);
auto res5a = res4 >>= triple;
auto res5b = res4 >>= triple >>= triple;
}
中的错误如下:
clang++
关于如何解决此问题的任何想法?
答案 0 :(得分:4)
这有效
auto f = std::function< result<int, std::string>(int)>(triple);
auto res5a = res4 >>= f;
我不能给出一个很好的简洁解释,仅是这么多:类型推导不考虑帐户转换,并且triple
是result<int,std::string>()(int)
而不是std::function
。
您不必使用std::function
,但是您可以接受任何可调用的内容,例如:
template <typename G>
auto operator>>=(G func) -> decltype(func(std::declval<S>())) {
if (pick == true) {
return func(succ);
} else {
return failure(fail);
}
}
请注意,std::function
会带来一些开销。它使用类型擦除来存储各种可调用对象。如果您只想传递一个可呼叫对象,则无需支付该费用。
对于第二行,@ Yksisarvinen的评论已经对其进行了总结。为了完整起见,我在这里简单引用它
auto res5b = res4 >>= triple >>= triple;
将无法工作 用于两个函数指针或显式括号的附加运算符 在res4 >>= triple
附近,因为operator >>=
是从右到左的。 它将首先尝试在三重和三重上应用>>=
。
PS:我都不知道,您的代码比我以前使用的功能风格还要多,也许您可以从std::conditional
中得到类似的东西?
答案 1 :(得分:2)
因此,在C ++中,std::function
不是任何感兴趣的基类。您无法从函数或lambda推论std::function
的类型。
所以您:
/// Chain two computations.
template <typename T>
result<T,F> operator>>=(std::function<result<T,F>(S)> func)
仅在通过实际的std::function
时才能推断出。
现在,您真正的意思是“带有S并为result<T,F>
类型返回T
的东西。”
这不是您用C ++所说的话。
如上所述,>>=
是右关联的。我可能会建议使用->*
,它从左到右。
第二,您的failure
静态函数无法正常工作,因为它经常返回错误的类型。
template<class F>
struct failure {
F t;
};
template<class F>
failure(F)->failure{F};
然后添加一个使用failure<F>
的构造函数。
/// Chain two computations.
template<class Self, class Rhs,
std::enable_if_t<std::is_same<result, std::decay_t<Self>>{}, bool> = true
>
auto operator->*( Self&& self, Rhs&& rhs )
-> decltype( std::declval<Rhs>()( std::declval<Self>().succ ) )
{
if (self.pick == true) {
return std::forward<Rhs>(rhs)(std::forward<Self>(self).succ);
} else {
return failure{std::forward<Self>(self).fail};
}
}
我现在正在仔细注意所涉及类型的r / lvalue值,如果可能的话,我们会采取行动。
template<class F>
struct failure {
F f;
};
template<class F>
failure(F&&)->failure<std::decay_t<F>>;
template<class S>
struct success {
S s;
};
template<class S>
success(S&&)->success<std::decay_t<S>>;
template <class S, class F>
class result
{
private:
std::variant<S, F> state;
public:
bool successful() const {
return state.index() == 0;
}
template<class Self,
std::enable_if_t< std::is_same<result, std::decay_t<Self>>{}, bool> = true
>
friend decltype(auto) s( Self&& self ) {
return std::get<0>(std::forward<Self>(self).state);
}
template<class Self,
std::enable_if_t< std::is_same<result, std::decay_t<Self>>{}, bool> = true
>
friend decltype(auto) f( Self&& self ) {
return std::get<1>(std::forward<Self>(self).state);
}
/// Chain results of two computations.
template<class Self, class Rhs,
std::enable_if_t< std::is_same<result, std::decay_t<Self>>{}, bool> = true
>
friend std::decay_t<Rhs> operator&&(Self&& self, Rhs&& rhs) {
if (self.successful()) {
return success{s(std::forward<Rhs>(rhs))};
} else {
return failure{f(std::forward<Self>(self))};
}
}
/// Chain two computations.
template<class Self, class Rhs,
std::enable_if_t< std::is_same<result, std::decay_t<Self>>{}, bool> = true
>
friend auto operator->*(Self&&self, Rhs&& rhs)
-> decltype( std::declval<Rhs>()( s( std::declval<Self>() ) ) )
{
if (self.successful()) {
return std::forward<Rhs>(rhs)(s(std::forward<Self>(self)));
} else {
return failure{f(std::forward<Self>(self))};
}
}
template<class T>
result( success<T> s ):
state(std::forward<T>(s.s))
{}
template<class T>
result( failure<T> f ):
state(std::forward<T>(f.f))
{}
explicit operator bool() const { return successful(); }
};
使用c++17。
答案 2 :(得分:1)
Result
C ++可以像haskell一样干净有效地表示Result
类型。像Haskell一样,C ++具有真正的 sum类型,我们可以用标记的并集封装其全部功能。另外,通过利用隐式构造,我们可以将Success
和Failure
表示为类型,而不是静态成员函数(这使事情更简洁)。
Success
和Failure
这些真的很简单。它们只是包装器类,因此我们可以将它们实现为聚合。另外,使用C ++ 17的模板推导指南,我们不必为Failure
和Success
指定模板参数。相反,我们只能编写Success{10}
或Failure{"Bad arg"}
。
template <class F>
class Failure {
public:
F value;
};
template<class F>
Failure(F) -> Failure<F>;
template <class S>
class Success {
public:
S value;
// This allows chaining from an initial Success
template<class Fun>
auto operator>>(Fun&& func) const {
return func(value);
}
};
template <class S>
Success(S) -> Success<S>;
Result
Result
是求和类型。这意味着它可以是成功或失败,但不能同时是两者。我们可以用并集来表示,并用was_success
bool标记。
template < class S, class F>
class Result {
union {
Success<S> success;
Failure<F> failure;
};
bool was_success = false; // We set this just to ensure it's in a well-defined state
public:
// Result overloads 1 through 4
Result(Success<S> const& s) : success(s), was_success(true) {}
Result(Failure<F> const& f) : failure(f), was_success(false) {}
Result(Success<S>&& s) : success(std::move(s)), was_success(true) {}
Result(Failure<F>&& f) : failure(std::move(f)), was_success(false) {}
// Result overloads 5 through 8
template<class S2>
Result(Success<S2> const& s) : success{S(s.value)}, was_success(true) {}
template<class F2>
Result(Failure<F2> const& f) : failure{F(f.value)}, was_success(false) {}
template<class S2>
Result(Success<S2>&& s) : success{S(std::move(s.value))}, was_success(true) {}
template<class F2>
Result(Failure<F2>&& f) : failure{F(std::move(f.value))}, was_success(false) {}
// Result overloads 9 through 10
Result(Result const&) = default;
Result(Result&&) = default;
template<class S2>
Result<S2, F> operator&&(Result<S2, F> const& res) {
if(was_success) {
return res;
} else {
return Failure{failure};
}
}
template<class Fun, class Ret = decltype(valueOf<Fun>()(success.value))>
auto operator>>(Fun&& func) const
-> Ret
{
if(was_success) {
return func(success.value);
} else {
return failure;
}
}
~Result() {
if(was_success) {
success.~Success<S>();
} else {
failure.~Failure<F>();
}
}
};
Result(...)
从成功或失败构造结果。
Success
和Failure
对象移动构造; std::string.
的情况Result
的移动和复制构造。 operator>>
这与您对operator>>=
的实现非常相似,我将解释更改背后的原因。
template<class Fun, class Ret = decltype(valueOf<Fun>()(success.value))>
auto operator>>(Fun&& func) const
-> Ret
{
if(was_success) {
return func(success.value);
} else {
return failure;
}
}
为什么不使用std::function
? std::function
是一种类型擦除包装器。这意味着它在后台使用虚函数调用,这会使事情变慢。通过使用不受约束的模板,我们使编译器更容易优化内容。
为什么使用>>
而不是>>=
?我使用>>
是因为>>=
的行为很奇怪,因为它是赋值运算符。语句a >>= b >>= c
实际上是a >>= (b >>= c)
,这不是我们想要的。
class Ret = decltype(valueOf<Fun>()(success.value))
做什么??它定义了一个模板参数,默认情况下为您传递的函数的返回类型。这使我们避免使用std::function
,同时也允许我们使用lambda。
~Result()
由于Result
包含一个并集,我们必须手动指定如何破坏它。 (包含Result
的类将不必执行此操作-一旦在Result
中指定了该名称,一切都将像正常情况一样)。这很简单。如果它包含Success
对象,我们将销毁该对象。否则,我们将销毁failure
。
// Result class
~Result() {
if(was_success) {
success.~Success<S>();
} else {
failure.~Failure<F>();
}
}
现在我们已经编写了Result
类,我们可以更新您对triple
和main
的定义。
triple
的新定义相当简单;我们只是将您的success
和failure
函数替换为Success
和Failure
类型。
auto triple(int val) -> Result<int, std::string>
{
if (val < 100) {
return Success{val * 3};
} else {
return Failure{"can't go over 100"};
}
}
main
的新定义我添加了一个print
函数,因此我们实际上可以看到一些输出。这只是一个lambda。完成了两次计算,一次用于ans
,另一次用于ans2
。 ans
的那个打印18,因为triple
的数字不会超过100,但是ans2
的那个打印任何东西,因为它会导致失败。
int main(int argc, char* argv[])
{
auto print = [](auto value) -> Result<decltype(value), std::string> {
std::cout << "Obtained value: " << value << '\n';
return Success{value};
};
auto ans = Success{2} >> triple >> triple >> print;
auto ans2 = Success{2} >> triple >> triple >> triple >> triple >> triple >> print;
}