12

Here's a testcase:

#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 rejects the conversion, claiming that there's no matching 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)

However, the code works as advertised when I declare/define operator>> inside namespace N.

Why's that? Why does the lookup otherwise fail?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 7
    Bog-standard ADL issue? – T.C. Aug 15 '16 at 14:58
  • @T.C.: Hmm... other `operator>>` found within namespace `N` therefore global namespace not searched and this particular `operator>>` not found? But I don't have another `operator>>` in `N`. Or any, in fact. I can't grok where ADL comes into it. – Lightness Races in Orbit Aug 15 '16 at 15:00
  • 1
    ADL comes into it because your `operator>>`'s second parameter type is `N::alarm_code_t`, so `N` is an associated namespace and will be searched for the operator definition. – Praetorian Aug 15 '16 at 15:12
  • 4
    @Praetorian The `>>` call happens in `namespace boost`. I think LRiO's question is "why isn't the `operator>>` in `namespace ::` *also* considered when `>>` is invoked in `namespace boost`?" Saying "it isn't found by ADL" only answers half of the question: ADL is not the only lookup. – Yakk - Adam Nevraumont Aug 15 '16 at 15:18

3 Answers3

15

Since the call to operator>> is made from boost::lexical_cast<> function template, the second argument to operator>> is a 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.

In other words, non-ADL lookup is not performed from the template instantiation context.

The global namespace is not considered because none of the arguments of the call have any association with the global namespace.

operator>>(std::istream& is, N::alarm_code_t& code) is not declared in namespace N, hence ADL does not find it.


These name-lookup oddities are documented in N1691 Explicit Namespaces.

Community
  • 1
  • 1
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
4

I rewrote the example a bit:

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

Now fake_operator for N::AC is not defined in FakeBoost, it's also not defined in N (so no ADL), so fake_cast won't find it.

The error message is a bit consusing (because boost). For my example it is:

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) {
      ^~~~~~~~~~~~~

Which explains a lot.

Adam Trhon
  • 2,915
  • 1
  • 20
  • 51
  • 1
    "Other overloads of `operator >>` are found because they use `using namespace std;` in boost." whoa, what? – T.C. Aug 15 '16 at 15:29
  • 1
    @T.C. That seems wrong, I see no `using namespace std` in the context that actually invokes `>>`: https://github.com/boostorg/lexical_cast/blob/develop/include/boost/lexical_cast/detail/converter_lexical_streams.hpp#L601 – Tavian Barnes Aug 15 '16 at 15:35
  • 1
    @T.C. I removed the note - it's really used in other contexts. But they do handle `std` - if you put `fake_operator` into `std` (just for science) it starts working – Adam Trhon Aug 15 '16 at 15:49
  • 4
    @Dadam That's because it will be found by ADL on the type `std::istream` or whatever – Tavian Barnes Aug 15 '16 at 19:20
  • 1
    @TavianBarnes right, there are *two* arguments available for ADL. Thank you! – Adam Trhon Aug 15 '16 at 20:40
2

Once operator>> is found in namespace boost, it stops looking in enclosed namespaces. It does, however, also do an ADL lookup.

#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{});
}

The above does not build.

Comment out void foo(int) {} and the code compiles. Your problem is the same, just with operators instead of foo.

Basically, operators not found by ADL are extremely fragile, you cannot rely on them.

live example.

A change in include order also breaks the lookup (if the foo(B::bar) is defined after the do_foo function, it cannot be found at the point of definition of do_foo nor by ADL), if the "already found a function named foo (or operator>>) does not break it. This is just part of the many ways non-ADL lookup in templates is fragile.

In short:

#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";
}

Also does not build with the same main, as at definiton of do_foo ::foo was not visible, and it will not be found via ADL as it is not in the namespace associated with B::bar .

Once foo is moved into namespace bar both cases work.

operator>> follows basically the same rules.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 1
    Even if `namespace boost` contains zero `operator>>`s, `::operator >>` still won't be found by ordinary unqualified lookup because that only considers the template definition context. – T.C. Aug 15 '16 at 15:28
  • @t.c. if you defined `>>` before including the header it would be found. ;) – Yakk - Adam Nevraumont Aug 15 '16 at 16:30