Wybór przeciążonej funkcji zadeklarowanej poniżej definicji szablonu.

1
#include <iostream>

void f(uint)
{
    std::cout << "uint" << std::endl;
}

template<typename T>
void g(T x) {
    f(x);  // Lookup of f here finds f(int)
}

struct S {};
void f(S)
{
    std::cout << "S" << std::endl;
}

void f(int)
{
    std::cout << "int" << std::endl;
}

void f(double)
{
    std::cout << "double" << std::endl;
}

int main() {
    g(0); 
    g<double>(0.0); 
    g(S()); 
}

Z jakiego powodu w definicji funkcji g linkujemy sie do razu z f ktore jest dostepne f(uint) ? Czy nie powinnismy zaczekac na template instantiation i jak mamy juz wszystkie definicje to stworzyc odpowiednia funkcje g i zlinkowac z poprawna wersja ? Nie jestem w stanie zrozumiec dlaczego kompilator zachowuje sie w taki dziwny sposob :/

2

Co do zasady, lookup dla funkcji nie-template w funkcji template jest wykonywany zaraz w momencie definicji tej funkcji template (w przeciwieństwie do tego, co dzieje się z funkcjami template). Zasada ta jest znana jako two-phase lookup. To jest powód, dla którego funkcja f jest szukana zaraz w momencie definicji funkcji g.

W Twoim przypadku, dla f(x) w g(T x), f(uint) jest jedyną funkcją f dostępną w momencie definicji g, dlatego jest ona wybrana jako kandydat.

Funkcje f(S), f(int), f(double) są zdefiniowane po g(T x), dlatego nie są widoczne w momencie definicji g.

Zeby Twoja funkcja g(T x) mogła korzystać z tych późniejszych definicji f, możesz przenieść definicje f(S), f(int), f(double) na początek pliku, przed definicją g(T x).

No albo przeciąż f jako funkcję template. Lookup dla funkcji template jest wykonany w momencie instancji, nie w momencie definicji.

0

Zeby bylo troche mniej zrozumiale uruchom cos takiego:

#include <iostream>

struct C {
  void f(uint)
  {
    std::cout << "uint" << std::endl;
  }

  template<typename T>
  void g(T x) {
    f(x);
  }

  struct S {};
  void f(S)
  {
    std::cout << "S" << std::endl;
  }

  void f(int)
  {
    std::cout << "int" << std::endl;
  }

  void f(double)
  {
    std::cout << "double" << std::endl;
  }
};

int main() {
  C c;
  c.g(0);
  c.g<double>(0.0);
  c.g(C::S());
}
0

Dziekuje wszystkim za szybka odpowiedz
Dla mnie wciaz do konca nie jest jasne jak zachowuje sie kompilator. Jak przeniose np deklaracje funkcji f dla wszystkich typow przed definicje g to wtedy kompilator natykajac sie na definicje g wygeneruje rozne definicje g uwzgledniajac wszystkie deklaracje f ? Czyli funkcja nie templatowa f bedzie troche traktowana jako funkcja templatowa ?
Ten przyklad wzial sie z odpowiedzi na pytanie https://stackoverflow.com/questions/76509738/unqualified-dependent-name-bind-in-definition-time-first-phase0/76509813#76509813 . Z tego by wynikalo ze nie rozumiem czym jest dependent names, bo zalozylem ze dependent names jest tylko wtedy kiedy funkcja jest templatowa.

2
Patryk Zc napisał(a):

Ten przyklad wzial sie z odpowiedzi na pytanie https://stackoverflow.com/questions/76509738/unqualified-dependent-name-bind-in-definition-time-first-phase0/76509813#76509813 . Z tego by wynikalo ze nie rozumiem czym jest dependent names, bo zalozylem ze dependent names jest tylko wtedy kiedy funkcja jest templatowa.

ADL nie ma nic do wyjaśnienia czemu void f(double) nie jest odnalezione.
ADL (argument dependent lookup) ma miejsce np tutaj:
https://godbolt.org/z/o7aPGTd8W

#include <iostream>
#include <string>

int main()
{
    std::string s;
    while(getline(std::cin, s))
    {
        std::cout << s << '\n';
    }

    return 0;
}

getline znajduje się w namespace std.
Normalnie kompilator by nie znalazł tej funkcji, bo nie ma żadnego using namespace std lub using std::getline, które sprowadzałoby ten symbol do bieżącego scope.
Jednak jako, że jeden z argumentów (tu akurat oba), jest typu pochodzącego z namespace std to kompilator przeszukuje też namespace std.

W problematycznym kodzie wszystko jest w global namespace ergo ADL nie ma tu nic do rzeczy.

Ja na razie nie potrafię wyjaśnić czemu void f(double) nie jest odnajdywane i używane jest void f(uint), a z void f(S) nie ma tego problemu.

To musi być jakoś związane z kolejnością deklaracji i faktem, domyślnej konwersji double do uint oraz int do uint.

1

Nieco światła może rzucać dodanie opcji: -Wconversion do kompilatora (gcc i clang):

<source>:12:7: warning: implicit conversion changes signedness: 'int' to 'uint' (aka 'unsigned int') [-Wsign-conversion]
    f(x);  // Lookup of f here finds f(int)
    ~ ^
<source>:32:5: note: in instantiation of function template specialization 'g<int>' requested here
    g(0); 
    ^
<source>:12:7: warning: implicit conversion turns floating-point number into integer: 'double' to 'uint' (aka 'unsigned int') [-Wfloat-conversion]
    f(x);  // Lookup of f here finds f(int)
    ~ ^
<source>:33:5: note: in instantiation of function template specialization 'g<double>' requested here
    g<double>(0.0); 
    ^
2 warnings generated.
ASM generation compiler returned: 0
<source>:12:7: warning: implicit conversion changes signedness: 'int' to 'uint' (aka 'unsigned int') [-Wsign-conversion]
    f(x);  // Lookup of f here finds f(int)
    ~ ^
<source>:32:5: note: in instantiation of function template specialization 'g<int>' requested here
    g(0); 
    ^
<source>:12:7: warning: implicit conversion turns floating-point number into integer: 'double' to 'uint' (aka 'unsigned int') [-Wfloat-conversion]
    f(x);  // Lookup of f here finds f(int)
    ~ ^
<source>:33:5: note: in instantiation of function template specialization 'g<double>' requested here
    g<double>(0.0); 
    ^
2 warnings generated.

https://godbolt.org/z/Gaj5Tn94q

1 użytkowników online, w tym zalogowanych: 0, gości: 1