我可能对std::overload提案和/或概念有点困惑,但根据我目前对两者的理解,我有以下问题:
为什么 C++20 不只是概念化/如果 constexprify std::visit,所以它根据传递给它的参数类型知道该怎么做。
例如,为什么我们不能让 std::visit 根据传递的参数概念修改它的行为(它所需要的只是函数在前,变体在后)。
因此,例如,这两个visit
都接受 3 个参数,但逻辑不同。
std::variant<int, double> v1=4.7;
std::variant<bool, std::string> v2=false;
// calls lambda that is a better fit(double one)
std::visit([](int& a){std::cout<< sizeof (a);},[](double& a){std::cout << a;} , v1);
// calls lambda on variant v1 and then variant v2
std::visit([](auto& a){}, v1, v2);
我认为这将是一个非常糟糕的主意。
visit
现在在概念上是一个简单的功能。它这样做:
template <typename F, typename... Variants>
decltype(auto) visit(F f, Variants... vs) {
return f(std::get<vs.index()>(vs)...);
}
当然,除了vs.index()
不是一个常量表达式,所以你不能只是这样做,你需要整个复杂的实现来做到这一点。但关键是 -visit
一堆变体只是调用f
每个变体的当前替代方案。这很容易推理。
让通用算法根据它传递的类型在语义上做不同的事情是一个非常糟糕的主意。这意味着你不能真正推理代码的正确性 - 因为它基本上可以做任何事情。OP 中的最后一个示例就是一个很好的例子:
std::visit([](auto&){}, v1, v2); // #1
std::visit([](auto&, auto&){}, v1, v2); // #2
今天,#1
(OP 示例)无法编译,因为您要将一元函数传递给二进制访问者。这是一件好事。但是有了这个建议,两者都会编译并做截然不同的事情。一个将按顺序访问每个变体,另一个将一起访问每个变体。
用户意图是什么?也许#1
是一个错误,用户要么忘记了参数,要么提供了一个在大多数情况下(但不是所有)都作为二元访问者工作的访问者,但在所有情况下都作为一元访问者工作?
OP 中的另一个例子不太糟糕,它具有:
visit(f1, f2, f3, v1, v2, v3)
表示对三个组合功能的三元访问,而不是对一个功能进行五元访问:
visit(overload(f1, f2, f3), v1, v2, v3)
但是,实现起来非常复杂,收益很小(用户可以将函数包装在他们这边,对吗?),你必须开始问这样的问题:如果你有一个可调用的variant
怎么办?
如果你打算使用不同的语法,我已经看到了几个代码示例,其中人们像这样实现访问语法:
visit2(v1, v2, v3)(f1, f2, f3)
这非常容易实现:
template <typename... Variants>
auto visit2(Variants&&... variants) {
return [&](auto&&... fs) -> decltype(auto) {
return std::visit(overload(FWD(fs)...), FWD(variants)...);
};
}
并且具有将变体放在首位而不必编写overload
的好处。也许这就足够了。