为什么在std :: visit中使用std :: overload而不是concepts / constexpr?

时间:2019-04-06 14:59:35

标签: c++ variant c++-concepts c++20

我可能对std :: overloaded提案和/或概念有些困惑,但是根据我对这两者的当前理解,我有以下问题:

为什么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); 

1 个答案:

答案 0 :(得分:4)

我认为这将是一个非常糟糕的主意。

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的好处。也许就足够了。