为什么 lexical_cast 要求运算符>>位于匹配的命名空间中?

Why does lexical_cast require the operator>> to be in a matching namespace?

这是一个测试用例:

#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),

(demo)

但是,当我在命名空间 N.

中 declare/define operator>> 时,代码会像宣传的那样工作

这是为什么?为什么查找会失败?

由于对 operator>> 的调用是从 boost::lexical_cast<> 函数模板进行的,因此 operator>> 的第二个参数是 dependent name:

Lookup rules

As discussed in lookup, the lookup of a dependent name used in a template is postponed until the template arguments are known, at which time

  • non-ADL lookup examines function declarations with external linkage that are visible from the template definition context

  • ADL examines function declarations with external linkage that are visible from both the template definition context and the template instantiation context

(in other words, adding a new function declaration after template definition does not make it visible, except via ADL)... The purpose of this is rule is to help guard against violations of the ODR for template instantiations.

换句话说,不从模板实例化上下文.

执行非ADL查找

不考虑全局命名空间,因为 none 个调用参数与全局命名空间有任何关联。

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


这些名称查找异常记录在 N1691 Explicit Namespaces

我稍微重写了这个例子:

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());
}

现在 N::ACfake_operator 没有在 FakeBoost 中定义,它也没有在 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) {
      ^~~~~~~~~~~~~

这解释了很多。

一旦在 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 未找到的运算符非常脆弱,您不能依赖它们。

live example.

包含顺序的更改也会中断查找(如果 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>>遵循基本相同的规则。