c++boostc++17c++20argument-dependent-lookup

using namespace std causes boost pointer cast to trigger ADL in c++17 standard


I have a simple code with inheritance and shared_ptr from boost library. With standard c++20, the code compiles fine. The function calls to static_pointer_cast and dynamic_pointer_cast compiles without prepended boost:: namespacing -- these function calls work because of ADL (Argument Dependent Lookup).

But with standard c++17, the code will not compile. I would assume that ADL wasn't implemented, or implemented differently, then. But then, if I add using namespace std, the code compiles fine. My question is: what does std have to do with boost library's function call?

Here is the online compiler so you can play around by commenting in and out the using namespace std; line: https://godbolt.org/z/cz8Md5Ezf

#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>

// using namespace std;

class Animal {
public:
  Animal()
  {}
  virtual ~Animal()
  {}
  void speak()
  {
    std::cout << "I am an animal\n";
  }
};

class Dog : public Animal {
public:
  Dog()
  {}
  void bark()
  {
    std::cout << "Gheu --> ";
  }
  void speak()
  {
    bark();
    Animal::speak();
  }
};

typedef boost::shared_ptr<Animal> AnimalPtr;
typedef boost::shared_ptr<Dog> DogPtr;

int main()
{
  AnimalPtr ap = boost::make_shared<Animal>();
  DogPtr dp = boost::make_shared<Dog>();

  AnimalPtr ap1 = static_pointer_cast<Animal>(dp);
  DogPtr dp1 = dynamic_pointer_cast<Dog>(ap1);

  return 0;
}

And if you are thinking, "Well, if using namespace std removes the errors, then those pointer casting calls must be from the std library." I thought so too. But looking through gdb, I saw that no, with using namespace std, those functions are definitely called from boost library.

enter image description here


Solution

  • When the compiler sees a < after an identifier in an expression (such as in static_pointer_cast<Animal>(dp)) it needs to figure out whether < refers to the relational less operator or whether it is the start of a template argument list.

    If a template is found by usual unqualified name lookup (not ADL), then the latter is assumed to be the case. But what if no template is found?

    Before C++20, if the lookup did not find a template, it was assumed that the name does not refer to a template and that the following < is therefore the less operator.

    In your original case that wouldn't make sense. For example there is nothing that static_pointer_cast can refer to as operand to <. So it will fail with a more or less clear error message depending on the compiler.

    With using namespace std; it may be possible that the compiler implicitly included std::static_pointer_cast (from <memory>) with one of the headers you included explicitly (there is no guarantee) or it may be that the boost headers include <memory>. If that happened, then the unqualified lookup for static_pointer_cast will find it and because it is a template, < after it will be considered to start a template argument list instead of being the relation operator. Then it is clear that the whole expression is an unqualified function call with template argument list.

    Since C++20, if the lookup does not find anything or finds any function at all, then it is also assumed that a < following the name introduces a template argument list.

    With the interpretation as template argument list and therefore function call, normal unqualified name lookup and ADL for unqualified function calls will be done, which finds both boost::shared_pointer_cast (via ADL) and std::shared_pointer_cast if you are using using namespace std;, but because you are passing a boost::shared_ptr the std version, which requires a std::shared_ptr argument, will not be viable and the boost version will always be chosen by overload resolution. This part is unchanged with C++20.

    Note that this is a breaking change in C++20. In some unusual situations you might have wanted < after a function name to actually refer to the relational operator which would now require parenthesizing the function name. See [diff.cpp17.temp] for an example.