为什么 lexical_cast 要求 operator>> 在匹配的命名空间中?

12
以下是一个测试用例:
#include <istream>
#include <boost/lexical_cast.hpp>

namespace N {
    enum class alarm_code_t {
        BLAH
    };
}

std::istream& operator>>(std::istream& is, N::alarm_code_t& code)
{
    std::string tmp;
    is >> tmp;

    if (tmp == "BLAH")
        code = N::alarm_code_t::BLAH;
    else
        is.setstate(std::ios::failbit);

    return is;
}

int main()
{
    auto code = boost::lexical_cast<N::alarm_code_t>("BLAH");
}

Boost拒绝转换,声称没有匹配的operator>>
In file included from /usr/local/include/boost/iterator/iterator_categories.hpp:22:0,
                 from /usr/local/include/boost/iterator/iterator_facade.hpp:14,
                 from /usr/local/include/boost/range/iterator_range_core.hpp:27,
                 from /usr/local/include/boost/lexical_cast.hpp:30,
                 from main.cpp:2:
/usr/local/include/boost/lexical_cast/detail/converter_lexical.hpp: In instantiation of 'struct boost::detail::deduce_target_char_impl<boost::detail::deduce_character_type_later<N::alarm_code_t> >':
/usr/local/include/boost/lexical_cast/detail/converter_lexical.hpp:270:89:   required from 'struct boost::detail::deduce_target_char<N::alarm_code_t>'
/usr/local/include/boost/lexical_cast/detail/converter_lexical.hpp:404:92:   required from 'struct boost::detail::lexical_cast_stream_traits<const char*, N::alarm_code_t>'
/usr/local/include/boost/lexical_cast/detail/converter_lexical.hpp:465:15:   required from 'struct boost::detail::lexical_converter_impl<N::alarm_code_t, const char*>'
/usr/local/include/boost/lexical_cast/try_lexical_convert.hpp:174:44:   required from 'bool boost::conversion::detail::try_lexical_convert(const Source&, Target&) [with Target = N::alarm_code_t; Source = char [5]]'
/usr/local/include/boost/lexical_cast.hpp:42:60:   required from 'Target boost::lexical_cast(const Source&) [with Target = N::alarm_code_t; Source = char [5]]'
main.cpp:25:60:   required from here
/usr/local/include/boost/lexical_cast/detail/converter_lexical.hpp:243:13: error: static assertion failed: Target type is neither std::istream`able nor std::wistream`able
             BOOST_STATIC_ASSERT_MSG((result_t::value || boost::has_right_shift<std::basic_istream<wchar_t>, T >::value),

(演示)

然而,当我在命名空间 N 中声明/定义 operator>> 时,代码按照预期工作。

为什么会这样?否则为什么查找失败?


7
常规的日常生活活动障碍问题? - T.C.
@T.C.:嗯...在命名空间N中找到其他的operator>>,因此没有搜索全局命名空间,也没有找到这个特定的operator>>?但是我在N中没有其他的operator>>。实际上,我没有任何一个。我无法理解ADL从哪里开始。 - Lightness Races in Orbit
1
ADL的作用是因为你的operator>>的第二个参数类型是N::alarm_code_t,所以N是一个关联的命名空间,将会被搜索操作符定义。 - Praetorian
4
“>>”调用发生在“boost”命名空间中。我认为LRiO的问题是:“当在‘boost’命名空间中调用‘>>’时,为什么‘::’命名空间中的‘operator>>’也没有被考虑?”仅回答“ADL找不到它”只回答了问题的一半:ADL不是唯一的查找方式。 - Yakk - Adam Nevraumont
3个回答

15

由于调用boost::lexical_cast<>函数模板中的operator>>,因此operator>>的第二个参数是依赖名称

查找规则

如查找所述,在模板中使用的依赖名称的查找被推迟到已知模板参数的时间,此时:

  • 非ADL查找会检查在模板定义上下文可见的具有外部链接的函数声明

  • ADL会检查在模板定义上下文模板实例化上下文中均可见的具有外部链接的函数声明

(换句话说,在模板实例化上下文中不执行非ADL查找)...此规则的目的是帮助防止模板实例化的ODR违规。

换句话说,从模板实例化上下文中不执行非ADL查找。

全局命名空间未被考虑,因为调用的参数与全局命名空间没有任何关联。

operator>>(std::istream& is, N::alarm_code_t& code)未在命名空间N中声明,因此ADL找不到它。


这些名称查找的奇特之处在N1691显式命名空间中有记录。


4

我稍微改写了这个例子:

namespace N {
    struct AC {};
}

namespace FakeBoost {

    template <typename T>
    void fake_cast(T t) {
        fake_operator(t);
    }

}

void fake_operator(N::AC ac) {
}

int main(){
     FakeBoost::fake_cast(N::AC());
}

现在在FakeBoost中没有定义N::ACfake_operator,也没有在N中定义(因此没有ADL),所以fake_cast找不到它。

错误信息有点混乱(因为boost)。对于我的示例,它是:

main.cpp: In instantiation of 'void FakeBoost::fake_cast(T) [with T = N::AC]':
main.cpp:19:33:   required from here
main.cpp:10:22: error: 'fake_operator' was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
     fake_operator(t);
     ~~~~~~~~~~~~~^~~
main.cpp:14:6: note: 'void fake_operator(N::AC)' declared here, later in the translation unit
 void fake_operator(N::AC ac) {
      ^~~~~~~~~~~~~

这解释了很多问题。


1
其他operator >>的重载是因为在boost中使用了using namespace std;。哇,这是什么意思? - T.C.
1
@T.C. 这似乎是错误的,我在实际调用 >> 的上下文中看不到 using namespace std:https://github.com/boostorg/lexical_cast/blob/develop/include/boost/lexical_cast/detail/converter_lexical_streams.hpp#L601 - Tavian Barnes
1
@T.C. 我已经删除了这个注释 - 它实际上在其他上下文中被使用。但是它们确实处理 std - 如果你把 fake_operator 放到 std 中(只是为了研究),它就开始工作了。 - Adam Trhon
4
@DadamиҝҷжҳҜеӣ дёәADLдјҡеңЁзұ»еһӢstd::istreamжҲ–д»»дҪ•е…¶д»–зұ»еһӢдёҠжҹҘжүҫе®ғгҖӮ - Tavian Barnes
1
@TavianBarnes 对,ADL有两个参数可用。谢谢! - Adam Trhon

2

一旦在namespace boost中找到operator>>,它就会停止在封闭的命名空间中查找。然而,它也会进行ADL查找。

#include <iostream>

namespace B{
  struct bar {};
}

void foo(B::bar) {
  std::cout << "foobar!\n";
}


namespace A{
  void foo(int) {}

  template<class T>
  void do_foo( T t ) {
    foo(t);
  }
}


int main() {
  A::do_foo(B::bar{});
}

以上代码无法编译。

void foo(int) {}注释掉,代码就可以编译。你的问题也是一样的,只是使用了运算符而不是foo

基本上,ADL找不到的操作符非常脆弱,您不能依赖它们。

实例

包含顺序的更改也会破坏查找(如果foo(B::bar)do_foo函数之后定义,则在定义do_foo时或通过ADL找不到它),如果“已经找到名为foo(或operator>>)的函数”不会破坏它。这只是模板中非ADL查找脆弱性的一部分。

简而言之:

#include <iostream>


namespace A{

// void foo(int) {}

  template<class T>
  void do_foo( T t ) {
    foo(t);
  }
}

namespace B{
  struct bar {};
}

void foo(B::bar) {
  std::cout << "foobar!\n";
}

同样的main无法构建,因为在定义do_foo::foo不可见,并且它不会通过ADL被找到,因为它不在与B::bar相关联的名称空间中。

一旦将foo移动到namespace bar中,两种情况都可以正常工作。

operator>>基本遵循相同的规则。


1
即使 namespace boost 中没有任何一个 operator>>,普通的未经限定的查找仍然无法找到 ::operator>>,因为它只考虑模板定义上下文。 - T.C.
如果在包含头文件之前定义了 >>,它就会被找到。 ;) - Yakk - Adam Nevraumont

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接