我可能对std ::重载的提议和/或概念感到有点困惑,但鉴于我目前对两者的理解,我有以下问题:
为什么C ++ 20不仅仅是构思/如果constexprify std :: visit所以它知道根据传递给它的参数类型做什么。
例如,为什么我们不能让std :: visit根据传递的参数的概念来修改它的行为(所有它需要的是函数先行,变量最后)。
因此,例如这两个visit
s接受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
ing一堆变种只是在每个变种的当前替代品上调用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)
意味着对三个组合函数进行三元访问,而不是一个函数上的quinary访问者:
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
。也许这还不错。