[英]Overloading operator on a templated class
Here is a definition of a Result
class that aims to simulate the logic of the Either monad from Haskell ( Left
is Failure
; Right
is Success
). 这是一个
Result
类的定义,该类旨在模拟Haskell中的Either monad的逻辑( Left
是Failure
; Right
是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;
}
};
When trying to compose two results using the &&
operator, all is well: 当尝试使用
&&
运算符组合两个结果时,一切都很好:
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;
}
But when attempting to chain computations on top of the result, a compilation error appears: 但是,当尝试在结果之上链接计算时,会出现编译错误:
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;
}
The error from clang++
is as follows: 来自
clang++
的错误如下:
minimal.cpp:82:21: error: no viable overloaded '>>='
auto res5a = res4 >>= triple;
~~~~ ^ ~~~~~~
minimal.cpp:26:17: note: candidate template ignored: could not match
'function<result<type-parameter-0-0, std::__1::basic_string<char,
std::__1::char_traits<char>, std::__1::allocator<char> > > (int)>' against
'result<int, std::__1::basic_string<char, std::__1::char_traits<char>,
std::__1::allocator<char> > > (*)(int)'
result<T,F> operator>>=(std::function<result<T,F>(S)> func) {
^
minimal.cpp:83:32: error: invalid operands to binary expression ('result<int,
std::string> (int)' and 'result<int, std::string> (*)(int)')
auto res5b = res4 >>= triple >>= triple;
Any idea as to how to fix this issue? 关于如何解决此问题的任何想法吗?
This works 这有效
auto f = std::function< result<int, std::string>(int)>(triple);
auto res5a = res4 >>= f;
I cannot give a good concise explanation, only that much: Type deduction does not take into acount conversions and triple
is a result<int,std::string>()(int)
not a std::function
. 我不能给出一个很好的简洁解释,仅是这么多:类型推导不考虑计数转换,而
triple
是一个result<int,std::string>()(int)
而不是std::function
。
You dont have to use std::function
but you can accept any callable with something like: 您不必使用
std::function
但可以接受类似以下内容的任何可调用对象:
template <typename G>
auto operator>>=(G func) -> decltype(func(std::declval<S>())) {
if (pick == true) {
return func(succ);
} else {
return failure(fail);
}
}
Note that std::function
comes with some overhead. 注意
std::function
带有一些开销。 It uses type erasure to be able to store all kinds of callables. 它使用类型擦除来存储各种可调用对象。 When you want to pass only one callable there is no need to pay that cost.
如果您只想传递一个可呼叫对象,则无需支付该费用。
For the second line @Yksisarvinen's comment already summarizes it. 对于第二行,@ Yksisarvinen的评论已经对其进行了总结。 For the sake of completeness I simply quote it here
为了完整起见,我在这里简单引用
auto res5b = res4 >>= triple >>= triple;
will not work without additional operator for two function pointers or an explicit brackets aroundres4 >>= triple
, becauseoperator >>=
is a right-to-left one.没有两个函数指针的附加运算符或
res4 >>= triple
周围的括号,将无法工作,因为operator >>=
是从右到左的。 It will try first to apply>>=
on triple and triple.它将首先尝试在三重和三重上应用
>>=
。
PS: I dont know Either and your code is a bit more functional style than what I am used to, maybe you can get similar out of std::conditional
? PS:我都不知道,而且您的代码比我以前使用的代码更具功能性,也许您可以从
std::conditional
得到类似的信息?
So, in C++, a std::function
is not the base class of anything of interest. 因此,在C ++中,
std::function
不是任何感兴趣的基类。 You cannot deduce the type of a std::function
from a function or a lambda. 您不能从函数或lambda推导
std::function
的类型。
So your: 因此,您的:
/// Chain two computations.
template <typename T>
result<T,F> operator>>=(std::function<result<T,F>(S)> func)
will only deduce when passed an actual std::function
. 仅在通过实际的
std::function
时才推断出来。
Now, what you really mean is "something that takes an S and returns a result<T,F>
for some type T
". 现在,您真正的意思是“带有S并为某些
T
类型返回result<T,F>
的东西”。
This isn't how you say it in C++. 这不是您在C ++中所说的。
As noted, >>=
is right-associative. 如前所述,
>>=
是右关联的。 I might propose ->*
instead, which is left-to-right. 我可能会建议
->*
,这是从左到右的。
Second, your failure
static function won't work right, as it returns the wrong type often. 其次,
failure
静态函数无法正常运行,因为它经常返回错误的类型。
template<class F>
struct failure {
F t;
};
template<class F>
failure(F)->failure{F};
then add a constructor taking a 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};
}
}
I am now carefully paying attention to r/lvalue ness of the types involved, and will move if possible. 我现在正在仔细注意所涉及类型的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(); }
};
live example . 现场例子 。
Result
cleanly and efficently Result
C++ can represent a Result
type just as cleanly and efficiently as haskell. C ++可以像haskell一样干净高效地表示
Result
类型。 Like Haskell, C++ has true sum types , and we can encapsulate the entirety of their functionality with a tagged union. 像Haskell一样,C ++具有真正的sum类型 ,我们可以使用加标签的并集封装其全部功能。 In addition, by taking advantage of implicit construction, we can represent
Success
and Failure
as types instead of static member functions (this makes things a lot cleaner). 另外,通过利用隐式构造,我们可以将
Success
和Failure
表示为类型,而不是静态成员函数(这使事情更加清晰)。
Success
and Failure
Success
与Failure
These are really simple. 这些真的很简单。 They're just wrapper classes, so we can implement them as aggregates.
它们只是包装器类,因此我们可以将它们实现为聚合。 In addition, using C++17's template deduction guides, we won't have to specify the template parameters for
Failure
and Success
. 此外,使用C ++ 17的模板推导指南,我们不必为
Failure
和Success
指定模板参数。 Instead, we'll just be able to write Success{10}
, or Failure{"Bad arg"}
. 相反,我们只能编写
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
Result
is a sum type. Result
是求和类型。 That means it can be either a success or a failure, but not both. 这意味着它可以是成功或失败,但不能同时是两者。 We can represent this with a union, which we'll tag with a
was_success
bool. 我们可以用并集来表示,并用
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(...)
Result(...)
A result is either constructed from a success or a failure. 从成功还是失败构造结果。
Success
and Failure
objects; Success
和Failure
对象移动构造。 std::string.
std::string.
Result
. Result
移动和复制结构。 operator>>
operator>>
This is pretty similar to your implementation of operator>>=
, and i'll explain the reasoning behind my changes. 这与您对
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;
}
}
Why not use std::function
? 为什么不使用
std::function
? std::function
is a type-erasing wrapper. std::function
是类型擦除包装器。 That means that it uses virtual function calls under the hood, which slow things down. 这意味着它在后台使用虚函数调用,这会使事情变慢。 By using an unconstrained template, we make it much easier for the compiler to optimize stuff.
通过使用不受约束的模板,我们使编译器更容易优化内容。
Why use >>
instead of >>=
? 为什么使用
>>
而不是>>=
? I used >>
because >>=
has weird behavior since it's an assignment operator. 我使用
>>
是因为>>=
具有奇怪的行为,因为它是赋值运算符。 The statement a >>= b >>= c
is actually a >>= (b >>= c)
, which is not what we intended. 语句
a >>= b >>= c
实际上a >>= (b >>= c)
,这不是我们想要的。
What does class Ret = decltype(valueOf<Fun>()(success.value))
do? class Ret = decltype(valueOf<Fun>()(success.value))
什么作用? That defines a template parameter which is defaulted to the return type of the function you pass. 它定义了一个模板参数,默认为您传递的函数的返回类型。 This enables us to avoid using
std::function
while also allowing us to use lambdas. 这使我们能够避免使用
std::function
同时还允许我们使用lambda。
~Result()
~Result()
Because Result
contains a union, we have to manually specify how to destruct it. 由于
Result
包含一个并集,因此我们必须手动指定如何对其进行销毁。 (Classes containing Result
won't have to do this - once we specify it inside Result
, everything behaves like normal). (包含
Result
类将不必执行此操作-一旦在Result
指定了Result
,则所有行为都将像正常情况一样)。 This is pretty straight-forward. 这很简单。 If it contains the
Success
object, we destroy that one. 如果它包含
Success
对象,我们将销毁该对象。 Otherwise, we destroy the failure
one. 否则,我们破坏
failure
之一。
// Result class
~Result() {
if(was_success) {
success.~Success<S>();
} else {
failure.~Failure<F>();
}
}
Now that we've written a Result
class, we can update your definitions of triple
and main
. 既然我们已经编写了一个
Result
类,我们就可以更新您对triple
和main
定义。
triple
triple
新定义 Pretty straight-forward; 很简单 we just replaced your
success
and failure
functions with the Success
and Failure
types. 我们只是将
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
main
新定义 I added a print
function so we can actually see some output. 我添加了
print
功能,因此我们实际上可以看到一些输出。 It's just a lambda. 这只是一个lambda。 There are two computations done, one for
ans
and the other for ans2
. 完成了两个计算,一个用于
ans
,另一个用于ans2
。 The one for ans
prints 18, because triple
doesn't push the number over 100, but the one for ans2
doesn't print anything because it results in a failure. 一个用于
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;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.