27 Nisan 2020 Pazartesi

Argument Dependent Lookup - Metod Çağrılarında Kullanılır

Giriş
Bu konu C++'taki bir çok lookup kavramlarından bir tanesi. Argument Dependent Lookup ADL olarak bilinir. ADL metod çağrılarında devreye girer. Açıklaması şöyle.
ADL happens with function calls
Unqualified Name Lookup yazısına da bakabilirsiniz. UNL sınıf isimlerine veya sınıf içindeki alanlara erişilmek istenince kullanılır.

ADL ile Önce Değişkenin İsim Alanındaki Metod Çağrılır
Açıklaması şöyle
"If an argument is a member of a namespace, the associated namespaces are the enclosing namespaces."
Açıklaması şöyle. Yani çağrılacak metod önce parametrenin isim alanında aranır. Eğer bulunmazsa global isim alanına bakılır.
These function names are looked up in the namespaces of their arguments in addition to the scopes and namespaces considered by the usual unqualified name lookup.
Örnek
En büyük problem şurada çıkıyor. STL isim alanındaki bir metodu override etmek isteyelim ancak bu metod STL isim alanında tanımlı bir nesne alıyor olsun. Yani elimizde şöyle bir kod olsun. std::string için kendi swap metodumuzun çağrılmadığını görürüz. Ancak eğer int ile deneseydik, çağrıldığını görürdük.
template <typename T>
void swap(T a , T b)
{
  T temp = a;
  a = b;
  b = temp;
}

int main()
{
  std::string first = "hi" , last = "Bye";
  swap(a,b);
  ...
}
Açıklaması şöyle.
std::swap is preferred over your version, because there exists an explicit specialization for std::basic_string type. If it didn't exist, call would be ambiguous probably.
For int, namespace std is not considered in the lookup process, so your version is the only acceptable.
Örnek - değişkenin isim alanındaki metod
Chrono isim alanındaki format metodu, Yine chrono Date parametresi ile çağrılabilir. Şöyle yaparız. Bu zaten ADL'in beklenen davranışı
namespace Chrono {
  class Date { /* ... */ };

  bool operator==(const Date&, const std::string&);

  std::string format(const Date&); // make string representation
    // ...
}

void f(Chrono::Date d, int i)
{
  std::string s = format(d); // Chrono::format()
  std::string t = format(i); // error: no format() in scope
}
Örnek - iki farklı isim alanında aynı isimli metod varsa
Elimizde iki farklı isim alanı olsun. Çıktı olarak adl alırız. Çünkü her ne kadar C isim alanı içinde de olsak A f metodu A isim alanı içindeki S ile çağrılıyor.
namespace A
{
  struct S{};
  void f(S){cout << "adl" << endl;}
}

namespace C
{
  void test()
  {
    A::S arg;
    f(arg);
  }
}

int main()
{
  C::test();
  return 0;
}
Örnek - global ve tanımlı isim alanında aynı metod
baz isimli iki metod olsun. İlki global isim alanında, ikincisi ise foo isim alanında. baz metodu foo isim alanındaki bar nesnesi ile çağrıldığı için önce foo isim alanındaki metod tercih edilir. Şöyle yaparız.
#include <stdio.h>
namespace foo {
  class bar {};
  void baz(bar&) {
    puts("foo::baz");
  }
}

void baz(const foo::bar&) {
  puts("::baz");
}

int main() {
  foo::bar bar;
  baz(bar);
}
Çıktı olarak foo::baz alırız.

Örnek - global ve tanımlı isim alanında aynı metod
Eğer iki farklı scope/namespace içinde aynı isimli metod varsa ilk bulduğunda durur.  Örnekte hem std::begin, hem de global begin scope içinde. std::begin'i ilk önce buluyor ve derleme hatası veriyor.
#include <iterator>
#include <iostream>

namespace AA{
    class AAA{};
};

using namespace AA;

int begin(AAA ){
    return 3;
}

int main(){
    using std::begin;  // to add std::begin in the name candidate set

    auto x = AAA();
    return begin(x); // compile error, ::begin cannot be found
}
Örnek - global ve tanımlı isim alanında aynı metod
Elimizde şöyle bir kod olsun. global parametre kullanılarak my isim alanında memcpy() çağrısı yapılıyor. Hem my isim alanındaki memcpy hem de global isim alanındaki memcpy seçilebildiği için derleme hatası alırız. Aynı şey my isim alanındaki BB ile denersek my isim alanında memcpy tercih edileceği için sorun olmaz.

Yukarıdaki örnekte bar nesnesi foo isim alanında olduğu için foo isim alanındaki metod tercih edilmişti.

Bu örnekte ise AA nesnesi global isim alanında olduğu için my isim alanındaki memcpy tercih edilemiyor.
namespace /*namespace name generated by compiler*/
{
  struct BB{};
}

struct AA{};

namespace my
{
  inline void * memcpy(void*, const void*, std::size_t)
  {
    puts("CUSTOM IMPLEMENTATION");
    return 0;
  }
}

namespace my
{
  void func()
  {
    AA a;
    memcpy(&a, &a, sizeof(a)); // ambigious call for g++4.7 - g++6.2

    BB b;
    memcpy(&b, &b, sizeof(b)); // unambigious call

  }
}

int main(int, char **)
{
  my::func();
  return 0;
}
Örnek
Bir başka örnek
namespace N {
  struct S { int i };
  void f(S);
  void g(S);
  void h(int);
}

struct Base {
  void f(N::S);
};

struct D : Base {
  void mf();
  void g(N::S x)
  {
    f(x); // call Base::f()
    mf(x); // call D::mf()
    h(1); // error: no h(int) available
  }
};
ADL ve İsim Alanından Daha Altta Olan Static Metodlar
Açıklaması şöyle
Argument-dependent lookup only finds declarations declared in namespaces, and only happens during unqualified lookup.We're not going to continue looking in classes declared in those associated namespaces for static functions or data members.
Örnek
Elimizde şöyle bir kod olsun. f metodu direkt nested_long_namespace_name içinde değil de useless yapısı içinde olduğu için ADL yapamıyoruz. Tüm yolu yazmak gerekiyor.
namespace nested_long_namespace_name
{
  struct type {};
  struct useless { static void f(type) {} };
}

int main()
{
  nested_long_namespace_name::type value{};
  f(value);                                      // ERROR (ADL does not kick in)
  useless::f(value);                             // ERROR (ADL does not kick in)
  nested_long_namespace_name::useless::f(value); // OK (but boy! this is a mouthful!)
}
Template
Açıklaması şöyle
And 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).
Anlaşılır dildeki açıklaması şöyle
Template are parsed in two phases.

In the first one, expression that are independent from the template arguments are resolved. In the second one, template dependent argument dependents are resolved where Argument dependent lookup is performed.
Elimizde şöyle bir kod olsun. Bu kod derlenmez.
template<class T> class List  
{
public: 
  void next(T*){
    cout<<"Doing some stuff"<<endl;
  }       
};

template<class T> class Special_List: public List<T>
{
public:
  void do_other_stuff(T* item){
    next(item);
  }       
};


int main(int argc, char *argv[])
{
  Special_List<int> b;
  int test_int = 3;
  b.do_other_stuff(&test_int);
}
Sebebi ise Special_List sınıfında next metodunun T parametresi ile ilgisiz olması. next metodu bir üst sınıfta aranır ancak bu sefer T ile ilgili olduğu için pas geçilir. Sonuç olarak derleme hatası alınır. Şu şekilde yaparsak
this->next(item);
next metodunun çözümlenmesi ikinci safhaya bırakılır ve bu sefer bulunabilir.







Hiç yorum yok:

Yorum Gönder