C++ 中的求和类型
Sum types in C++
在工作中,我 运行 遇到这样一种情况,其中描述函数返回结果的最佳类型是 std::variant<uint64_t, uint64_t>
- 当然,这不是有效的 C++,因为您可以' 有两个相同类型的变体。我可以将其表示为 std::pair<bool, uint64_t>
,或者该对的第一个元素是一个枚举,但这是一种特殊情况; std::variant<uint64_t, uint64_t, bool>
不能很好地表示,我的函数式编程背景确实让我想要 Either
- 所以我尝试使用访客模式来实现它,就像我在其他模式中所做的那样不支持和类型的语言:
template <typename A, typename B, typename C>
class EitherVisitor {
virtual C onLeft(const A& left) = 0;
virtual C onRight(const B& right) = 0;
};
template <typename A, typename B>
class Either {
template <typename C>
virtual C Accept(EitherVisitor<A, B, C> visitor) = 0;
};
template <typename A, typename B>
class Left: Either<A, B> {
private:
A value;
public:
Left(const A& valueIn): value(valueIn) {}
template <typename C>
virtual C Accept(EitherVisitor<A, B, C> visitor) {
return visitor.onLeft(value);
}
};
template <typename A, typename B>
class Right: Either<A, B> {
private:
B value;
public:
Right(const B& valueIn): value(valueIn) {}
template <typename C>
virtual C Accept(EitherVisitor<A, B, C> visitor) {
return visitor.onRight(value);
}
};
C++ 拒绝这个,因为模板方法 Accept
不能是虚拟的。是否有针对此限制的变通方法,使我能够根据其 f 代数和变形正确地表示基本和类型?
std::variant<X,X>
是有效的 C++。
用起来有点别扭,因为std::visit
不给你索引,std::get<X>
也不行
解决这个问题的方法是创建一个变体索引,就像一个强大的枚举。
template<std::size_t i>
using index_t = std::integral_constant<std::size_t, i>;
template<std::size_t i>
constexpr index_t<i> index = {};
template<std::size_t...Is>
using number = std::variant< index_t<Is>... >;
namespace helpers {
template<class X>
struct number_helper;
template<std::size_t...Is>
struct number_helper<std::index_sequence<Is...>> {
using type=number<Is...>;
};
}
template<std::size_t N>
using alternative = typename helpers::number_helper<std::make_index_sequence<N>>::type;
然后我们可以从变体中提取备选方案:
namespace helpers {
template<class...Ts, std::size_t...Is, class R=alternative<sizeof...(Ts)>>
constexpr R get_alternative( std::variant<Ts...> const& v, std::index_sequence<Is...> ) {
constexpr R retvals[] = {
R(index<Is>)...
};
return retvals[v.index()];
}
}
template<class...Ts>
constexpr alternative<sizeof...(Ts)> get_alternative( std::variant<Ts...> const& v )
{
return helpers::get_alternative(v, std::make_index_sequence<sizeof...(Ts)>{});
}
所以现在你有一个 std::variant<int, int>
,你可以
auto which = get_alternative( var );
和which
是一个变体,在运行时由一个整数表示,它是var
中活动类型的索引。您可以:
std::variant<int, int> var( std::in_place_index_t<1>{}, 7 );
auto which = get_alternative( var );
std::visit( [&var](auto I) {
std::cout << std::get<I>(var) << "\n";
}, get_alternative(var) );
并访问 var
中的哪些替代可能性是活跃的,具有编译时间常数。
我发现 get_alternative(variant)
使 variant<X,X,X>
更有用,并填补了我认为您可能会 运行 的漏洞。
现在如果你不需要一个活跃的编译时索引,你可以调用var.index()
,然后通过visit( lambda, var )
.
访问
构造变体时,确实需要编译时索引来做variant<int, int> var( std::in_place_index_t<0>{}, 7 )
。措辞 有点 尴尬,因为虽然 C++ 支持同一类型的倍数的变体,但它认为它们比通用代码之外的“标准”不相交变体更不可能。
但是我以前用过这个alternative
和get_alternative
之类的代码来支持像数据胶水代码这样的函数式编程。
也许最简单的解决方案是围绕 T
对 Right
和 Left
进行轻量级包装?
基本上是一个强类型别名(也可以使用 Boost 的 strong typedef)
template<class T>
struct Left
{
T val;
};
template<class T>
struct Right
{
T val;
};
然后我们可以区分它们进行访问:
template<class T, class U>
using Either = std::variant<Left<T>, Right<U>>;
Either<int, int> TrySomething()
{
if (rand() % 2 == 0) // get off my case about rand(), I know it's bad
return Left<int>{0};
else
return Right<int>{0};
}
struct visitor
{
template<class T>
void operator()(const Left<T>& val_wrapper)
{
std::cout << "Success! Value is: " << val_wrapper.val << std::endl;
}
template<class T>
void operator()(const Right<T>& val_wrapper)
{
std::cout << "Failure! Value is: " << val_wrapper.val << std::endl;
}
};
int main()
{
visitor v;
for (size_t i = 0; i < 10; ++i)
{
auto res = TrySomething();
std::visit(v, res);
}
}
在工作中,我 运行 遇到这样一种情况,其中描述函数返回结果的最佳类型是 std::variant<uint64_t, uint64_t>
- 当然,这不是有效的 C++,因为您可以' 有两个相同类型的变体。我可以将其表示为 std::pair<bool, uint64_t>
,或者该对的第一个元素是一个枚举,但这是一种特殊情况; std::variant<uint64_t, uint64_t, bool>
不能很好地表示,我的函数式编程背景确实让我想要 Either
- 所以我尝试使用访客模式来实现它,就像我在其他模式中所做的那样不支持和类型的语言:
template <typename A, typename B, typename C>
class EitherVisitor {
virtual C onLeft(const A& left) = 0;
virtual C onRight(const B& right) = 0;
};
template <typename A, typename B>
class Either {
template <typename C>
virtual C Accept(EitherVisitor<A, B, C> visitor) = 0;
};
template <typename A, typename B>
class Left: Either<A, B> {
private:
A value;
public:
Left(const A& valueIn): value(valueIn) {}
template <typename C>
virtual C Accept(EitherVisitor<A, B, C> visitor) {
return visitor.onLeft(value);
}
};
template <typename A, typename B>
class Right: Either<A, B> {
private:
B value;
public:
Right(const B& valueIn): value(valueIn) {}
template <typename C>
virtual C Accept(EitherVisitor<A, B, C> visitor) {
return visitor.onRight(value);
}
};
C++ 拒绝这个,因为模板方法 Accept
不能是虚拟的。是否有针对此限制的变通方法,使我能够根据其 f 代数和变形正确地表示基本和类型?
std::variant<X,X>
是有效的 C++。
用起来有点别扭,因为std::visit
不给你索引,std::get<X>
也不行
解决这个问题的方法是创建一个变体索引,就像一个强大的枚举。
template<std::size_t i>
using index_t = std::integral_constant<std::size_t, i>;
template<std::size_t i>
constexpr index_t<i> index = {};
template<std::size_t...Is>
using number = std::variant< index_t<Is>... >;
namespace helpers {
template<class X>
struct number_helper;
template<std::size_t...Is>
struct number_helper<std::index_sequence<Is...>> {
using type=number<Is...>;
};
}
template<std::size_t N>
using alternative = typename helpers::number_helper<std::make_index_sequence<N>>::type;
然后我们可以从变体中提取备选方案:
namespace helpers {
template<class...Ts, std::size_t...Is, class R=alternative<sizeof...(Ts)>>
constexpr R get_alternative( std::variant<Ts...> const& v, std::index_sequence<Is...> ) {
constexpr R retvals[] = {
R(index<Is>)...
};
return retvals[v.index()];
}
}
template<class...Ts>
constexpr alternative<sizeof...(Ts)> get_alternative( std::variant<Ts...> const& v )
{
return helpers::get_alternative(v, std::make_index_sequence<sizeof...(Ts)>{});
}
所以现在你有一个 std::variant<int, int>
,你可以
auto which = get_alternative( var );
和which
是一个变体,在运行时由一个整数表示,它是var
中活动类型的索引。您可以:
std::variant<int, int> var( std::in_place_index_t<1>{}, 7 );
auto which = get_alternative( var );
std::visit( [&var](auto I) {
std::cout << std::get<I>(var) << "\n";
}, get_alternative(var) );
并访问 var
中的哪些替代可能性是活跃的,具有编译时间常数。
我发现 get_alternative(variant)
使 variant<X,X,X>
更有用,并填补了我认为您可能会 运行 的漏洞。
现在如果你不需要一个活跃的编译时索引,你可以调用var.index()
,然后通过visit( lambda, var )
.
构造变体时,确实需要编译时索引来做variant<int, int> var( std::in_place_index_t<0>{}, 7 )
。措辞 有点 尴尬,因为虽然 C++ 支持同一类型的倍数的变体,但它认为它们比通用代码之外的“标准”不相交变体更不可能。
但是我以前用过这个alternative
和get_alternative
之类的代码来支持像数据胶水代码这样的函数式编程。
也许最简单的解决方案是围绕 T
对 Right
和 Left
进行轻量级包装?
基本上是一个强类型别名(也可以使用 Boost 的 strong typedef)
template<class T>
struct Left
{
T val;
};
template<class T>
struct Right
{
T val;
};
然后我们可以区分它们进行访问:
template<class T, class U>
using Either = std::variant<Left<T>, Right<U>>;
Either<int, int> TrySomething()
{
if (rand() % 2 == 0) // get off my case about rand(), I know it's bad
return Left<int>{0};
else
return Right<int>{0};
}
struct visitor
{
template<class T>
void operator()(const Left<T>& val_wrapper)
{
std::cout << "Success! Value is: " << val_wrapper.val << std::endl;
}
template<class T>
void operator()(const Right<T>& val_wrapper)
{
std::cout << "Failure! Value is: " << val_wrapper.val << std::endl;
}
};
int main()
{
visitor v;
for (size_t i = 0; i < 10; ++i)
{
auto res = TrySomething();
std::visit(v, res);
}
}