1

I am trying to migrate some code from boost::tuple to std::tuple but I'm getting some weird errors: after I invoke using namespace std (and never boost), I expect an unqualified tie to resolve to std::tie. However, this seems to fail when the tuple contains a boost container pointer, for example.

#include <tuple>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/identity.hpp>

#ifdef USE_STD
#define TIE std::tie
#else
#define TIE tie
#endif

typedef boost::multi_index_container<
  int,
  boost::multi_index::indexed_by<
    boost::multi_index::ordered_non_unique<
      boost::multi_index::identity<int>
    >
  >
> Set;

std::tuple< int, int* > make_i_ptr();
std::tuple< int, Set* > make_set();

int main()
{
    using namespace std;
    int i;
    int* i_ptr;
    Set* set_ptr;
    tie(i, i_ptr) = make_i_ptr();
    TIE(i, set_ptr) = make_set();
    return 0;
}

If I compile with g++ -std=c++0x -c test.cpp -DUSE_STD, all is well. However, without -D USE_STD, I get compile errors suggesting g++ tries to use boost::tuples::tie. I'm using g++ 4.8.1 and boost 1.55.0. Do you think this is a bug with boost? Or is there some spec I'm missing?

Matei David
  • 2,322
  • 3
  • 23
  • 36
  • 6
    It's ADL. Use a qualified name for both. – chris Nov 18 '13 at 23:46
  • 6
    "after I invoke `using namespace std`" - ***NO*** – Xeo Nov 19 '13 at 00:03
  • @Xeo: I'm not in a header file. I.e., IRL, the fcn definitions are in header files with no `using`, that's why I put it inside `main()` here. Beyond that it's a preference not a rule. – Matei David Nov 19 '13 at 00:09
  • You say "Doctor, it hurts when I do that." Doctor says "Don't do that." This really is just how things work (and not a bug in Boost). – Nevin Nov 19 '13 at 00:14
  • @chris: Thanks, I didn't know about ADL. But I don't get it: it sounds like ADL should help the compiler resolve names. If ADL makes it resolve to boost::tie for... "my convenience" and then the compilation fails, shouldn't that be a clue to the compiler that it picked the wrong function?! – Matei David Nov 19 '13 at 00:14
  • @mmd, There are specific rules about when it happens and when it doesn't. This is a time where the extra candidate for overload resolution (which is a later phase, mind you) isn't so appreciated. – chris Nov 19 '13 at 00:31
  • Re: `using namespace` and the doctor analogy... I asked my question here in order to understand why I am getting compile errors in this case (apparently ADL). I know I could add `std::` and not get errors, or I could also go play guitar and not get errors. Neither of those observations are helpful though. – Matei David Nov 19 '13 at 00:33
  • Seems pretty similar to http://stackoverflow.com/questions/19802173/overloading-function-doesnt-work-with-complex. – chris Nov 19 '13 at 00:49

1 Answers1

4

Lookup is complicated. The problem as others have mentioned is Argument Dependent Lookup or ADL. The rules for ADL were added to allow the definition of operators in the same namespace as the types they refer to, and enabling lookup to find those operators when present. This was later extended to all other functions in the process of standarization.

The issue here is that tie(...) is a function call. The compiler will attempt regular lookup from the point of use (inside main) it will move out to the enclosing namespace. The using directive will add ::std into the lookup search when it hits the root namespace (common ancestor of ::std and ::main). At that point, since the identifier resolves to a function, ADL will kick in.

ADL adds the namespaces associated to the arguments of the function call, which in this case is boost:: (fundamental types like int have no associated namespaces). At this point the compiler sees two declarations of tie: std::tie and boost::tie, causing ambiguity.

As you already know the solution is to qualify the call to std::tie (which I would advice that you use even without this issue). Regarding the comment:

If ADL makes it resolve to boost::tie for... "my convenience" and then the compilation fails, shouldn't that be a clue to the compiler that it picked the wrong function?!

I don't know what the exact error you are getting is (I don't use boost, and I don't know what possible overloads of std::tie it contains). If the problem is indeed one of ambiguity, the problem is that the compiler cannot resolve the identifier, and cannot continue the process. At that point it stops and asks for the programmer to resolve it. If the error is that it uniquely picked boost::tie and it later failed in the assignment, it means that theres is an overload of boost::tie that is a better match than std::tie and that was selected. At a later time the assignment from std::tuple may have failed, but the compiler cannot know whether the problem was during lookup, or whether it is the assignment itself (did you intend on assigning that variable? maybe a different one?) so again it fails and tells you what the problem is.

Note that in general the process of compilation is always moving forward, the compiler does not backtrack to double guess its own decisions*. There is a set of rules, and those rules are applied at each step. If there is ambiguity, compilation stops, if not, then there is a single best candidate and this point is resolved, moving to the next. Attempting to go back to undo decisions would turn the compilation process into something painfully slow (the number of paths that could be taken would be exponential).

* As always there are some exceptions but those are just exceptions, notably during overload resolution if a template is picked as the best candidate but substitution of type arguments fail, it is discarded and the next best candidate is chosen.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • You are right, the error I'm getting (sadly, they are not concise enough to post, I love C++ except for template-related these errors...) was about `tuple` assignment. So `tie` resolves to `boost::tuples::tie`, which produces a `boost::tuples::tuple` lvalue, on which `operator =` runs, with `std::tuple` as rvalue, and that generates the error. – Matei David Nov 19 '13 at 16:24
  • Re: compilation moving only forward: From what I gather, the purpose of ADL is to allow concise syntax for operators such as `std::string` addition. It is weird that in this case, ADL mandates less concise syntax. I still think the compiler could be slightly smarter: all it needs to do is realize that the correct `tie` name resolution at that point should also allow for something like `operator =(const std::tuple&)` to be applied. It's been many years since I designed a mock compiler for a course project, perhaps gcc is more elaborate than that one :D – Matei David Nov 19 '13 at 16:33
  • 1
    @mmd: The purpose of ADL is **not** making the code more concise, it was designed to allow lookup to find operators (for which you **cannot** provide qualification in general) for types defined in different namespaces. You ca (ab)use it to avoid qualification, but that is a side effect of the fact that the committee extended Andrew's approach to *all* functions, rather than only operators (that was the original intent). The reason that you *cannot* qualify operators is that the syntax for a qualified call depends on how the operator is defined: `ns::operator+(a,b)` vs. `a.operator+(b)` – David Rodríguez - dribeas Nov 19 '13 at 21:28
  • ... of course part of the design criteria was to enable the use as just the operator, and that *is* making the code more concise `a+b` being more concise than any of the alternatives that require qualification. – David Rodríguez - dribeas Nov 19 '13 at 21:29