g++ std::visit 泄漏到全局命名空间?
g++ std::visit leaking into global namespace?
我刚刚在 std::visit 和 std::function 附近遇到了一些微妙的事情,这让我很困惑。我并不孤单,但我能找到的唯一其他人跳了 "workaround and move on" 舞,这对我来说还不够:
这可能与 LWG 中的一个未解决问题有关,但我认为这里发生了更险恶的事情:
最小示例:
// workaround 1: don't include <variant>
#include <variant>
#include <functional>
struct Target
{
Target *next = nullptr;
};
struct Visitor
{
void operator()(const Target &tt) const { }
};
// workaround 2: concretely use 'const Visitor &' instead of 'std::function<...>'
void visit(const Target &target, const std::function<void(const Target &)> &visitor)
{
visitor(target);
if(target.next)
visit(*target.next,visitor); // workaround 3: explicitly invoke ::visit(...)
//^^^ problem: compiler is trying to resolve this as std::visit(...)
}
int main(int argc, char **argv, char **envp)
{
return 0;
}
使用 g++ -std=c++17 编译,测试使用:
- g++-7 (Ubuntu 7.5.0-3ubuntu1~18.04)
- g++-8 (Ubuntu 8.4.0-1ubuntu1~18.04)
最终结果是编译器尝试使用 std::visit 来调用 clearly-not-std visit(*target.next,visitor):
g++-8 -std=c++17 -o wtvariant wtvariant.cpp
In file included from sneakyvisitor.cpp:3:
/usr/include/c++/8/variant: In instantiation of ‘constexpr decltype(auto) std::visit(_Visitor&&, _Variants&& ...) [with _Visitor = Target&; _Variants = {const std::function<void(const Target&)>&}]’:
wtvariant.cpp:20:31: required from here
/usr/include/c++/8/variant:1385:23: error: ‘const class std::function<void(const Target&)>’ has no member named ‘valueless_by_exception’
if ((__variants.valueless_by_exception() || ...))
~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/8/variant:1390:17: error: no matching function for call to ‘get<0>(const std::function<void(const Target&)>&)’
std::get<0>(std::forward<_Variants>(__variants))...));
~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
在我的真实用例中,我以为有人偷偷把 "using namespace std" 偷偷塞进了我的树的 header space 中,我会很生气。然而,这个最小的例子证明了不同。
关键问题:鉴于我没有创建或使用任何名称space,为什么 std::visit(...) 会参与其中?
- WRT 解决方法 1:至少在变体 header 中,visit(...) 在标准名称 space
中正确声明
- WRT 解决方法 2:如果第二个参数不是 std::function,它编译得很好,让我相信这里发生了一些更微妙的事情。
- WRT 解决方法 3:我知道两个冒号是一个很小的代价,但考虑到我对放置像 visit(... ) 变成一个名字space.
三个标记的解决方法中的任何一个都会抑制编译器错误,但我个人不能容忍我无法解决的语言故障(虽然我理解必要性,但我仍然对如何解决感到不安通常我必须在模板中添加 'typename' 才能编译它们。
另外值得注意的是,如果尝试在没有限定的情况下使用 std 名称的其他元素space(例如,尝试裸 'cout'),编译器会适当地抱怨无法弄清楚我所追求的 'cout',所以变体 header 并不是以某种方式压扁标准名称 space.
最后,即使我将 visit() 方法放在它自己的名称中,这个问题仍然存在space:编译器真的想使用 std::visit(...) 除非我显式调用 my_namespace::visit(...).
我错过了什么?
参数 visitor
是一个 std::function
,它在命名空间 std
中,因此 argument-dependent lookup 在命名空间 [=12= 中找到 visit
] 还有。
如果您总是希望在全局命名空间中使用 visit
,请使用 ::visit
。
我刚刚在 std::visit 和 std::function 附近遇到了一些微妙的事情,这让我很困惑。我并不孤单,但我能找到的唯一其他人跳了 "workaround and move on" 舞,这对我来说还不够:
这可能与 LWG 中的一个未解决问题有关,但我认为这里发生了更险恶的事情:
最小示例:
// workaround 1: don't include <variant>
#include <variant>
#include <functional>
struct Target
{
Target *next = nullptr;
};
struct Visitor
{
void operator()(const Target &tt) const { }
};
// workaround 2: concretely use 'const Visitor &' instead of 'std::function<...>'
void visit(const Target &target, const std::function<void(const Target &)> &visitor)
{
visitor(target);
if(target.next)
visit(*target.next,visitor); // workaround 3: explicitly invoke ::visit(...)
//^^^ problem: compiler is trying to resolve this as std::visit(...)
}
int main(int argc, char **argv, char **envp)
{
return 0;
}
使用 g++ -std=c++17 编译,测试使用:
- g++-7 (Ubuntu 7.5.0-3ubuntu1~18.04)
- g++-8 (Ubuntu 8.4.0-1ubuntu1~18.04)
最终结果是编译器尝试使用 std::visit 来调用 clearly-not-std visit(*target.next,visitor):
g++-8 -std=c++17 -o wtvariant wtvariant.cpp
In file included from sneakyvisitor.cpp:3:
/usr/include/c++/8/variant: In instantiation of ‘constexpr decltype(auto) std::visit(_Visitor&&, _Variants&& ...) [with _Visitor = Target&; _Variants = {const std::function<void(const Target&)>&}]’:
wtvariant.cpp:20:31: required from here
/usr/include/c++/8/variant:1385:23: error: ‘const class std::function<void(const Target&)>’ has no member named ‘valueless_by_exception’
if ((__variants.valueless_by_exception() || ...))
~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/8/variant:1390:17: error: no matching function for call to ‘get<0>(const std::function<void(const Target&)>&)’
std::get<0>(std::forward<_Variants>(__variants))...));
~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
在我的真实用例中,我以为有人偷偷把 "using namespace std" 偷偷塞进了我的树的 header space 中,我会很生气。然而,这个最小的例子证明了不同。
关键问题:鉴于我没有创建或使用任何名称space,为什么 std::visit(...) 会参与其中?
- WRT 解决方法 1:至少在变体 header 中,visit(...) 在标准名称 space 中正确声明
- WRT 解决方法 2:如果第二个参数不是 std::function,它编译得很好,让我相信这里发生了一些更微妙的事情。
- WRT 解决方法 3:我知道两个冒号是一个很小的代价,但考虑到我对放置像 visit(... ) 变成一个名字space.
三个标记的解决方法中的任何一个都会抑制编译器错误,但我个人不能容忍我无法解决的语言故障(虽然我理解必要性,但我仍然对如何解决感到不安通常我必须在模板中添加 'typename' 才能编译它们。
另外值得注意的是,如果尝试在没有限定的情况下使用 std 名称的其他元素space(例如,尝试裸 'cout'),编译器会适当地抱怨无法弄清楚我所追求的 'cout',所以变体 header 并不是以某种方式压扁标准名称 space.
最后,即使我将 visit() 方法放在它自己的名称中,这个问题仍然存在space:编译器真的想使用 std::visit(...) 除非我显式调用 my_namespace::visit(...).
我错过了什么?
参数 visitor
是一个 std::function
,它在命名空间 std
中,因此 argument-dependent lookup 在命名空间 [=12= 中找到 visit
] 还有。
如果您总是希望在全局命名空间中使用 visit
,请使用 ::visit
。