Как работает разрешение перегрузки, когда аргумент является перегруженной функцией?

преамбула

Разрешение перегрузки в C ++ может быть слишком сложным процессом. Требуется немало умственных усилий, чтобы понять все правила C ++, которые управляют разрешением перегрузки. Недавно мне пришло в голову, что наличие имени перегруженной функции в списке аргументов может усложнить разрешение перегрузки. Так как это был широко используемый случай, я отправилвопрос и получил ответ, который позволил мне лучше понять механику этого процесса. Однако формулировка этого вопроса в контексте iostreams, похоже, несколько отвлекла внимание от самой сути решаемой проблемы. Таким образом, я начал углубляться и нашел другие примеры, которые требуют более детального анализа проблемы. Этот вопрос является вводным и сопровождаетсяболее сложный.

Вопрос

Предположим, что каждый полностью понимает, как работает разрешение перегрузки при отсутствии аргументов, которые сами являются именами перегруженных функций. Какие изменения необходимо внести в их понимание разрешения перегрузки, чтобы оно также охватывало случаи, когда перегруженные функции используются в качестве аргументов?

Примеры

Учитывая эти заявления:

void foo(int) {}
void foo(double) {}
void foo(std::string) {}
template<class T> void foo(T* ) {}

struct A {
    A(void (*)(int)) {}
};

void bar(int x, void (*f)(int)) {}
void bar(double x, void (*f)(double)) {}
void bar(std::string x, void (*f)(std::string)) {}
template<class T> void bar(T* x, void (*f)(T*)) {}
void bar(A x, void (*f2)(double)) {}

Приведенные ниже выражения приводят к следующему разрешению имениfoo (хотя бы с gcc 5.4):

bar(1, foo); // foo(int)
             // but if foo(int) is removed, foo(double) takes over

bar(1.0, foo); // foo(double)
               // but if foo(double) is removed, foo(int) takes over

int i;
bar(&i, foo); // foo<int>(int*)

bar("abc", foo); // foo<const char>(const char*)
                 // but if foo<T>(T*) is removed, foo(std::string) takes over

bar(std::string("abc"), foo); // foo(std::string)

bar(foo, foo); // 1st argument is foo(int), 2nd one - foo(double)
Код для игры:
#include <iostream>
#include <string>

#define PRINT_FUNC  std::cout << "\t" << __PRETTY_FUNCTION__ << "\n";

void foo(int)                      { PRINT_FUNC; }
void foo(double)                   { PRINT_FUNC; }
void foo(std::string)              { PRINT_FUNC; }
template<class T> void foo(T* )    { PRINT_FUNC; }

struct A { A(void (*f)(int)){ f(0); } };

void bar(int         x, void (*f)(int)        ) { f(x); }
void bar(double      x, void (*f)(double)     ) { f(x); }
void bar(std::string x, void (*f)(std::string)) { f(x); }
template<class T> void bar(T* x, void (*f)(T*)) { f(x); }
void bar(A, void (*f)(double)) { f(0); }

#define CHECK(X) std::cout << #X ":\n"; X; std::cout << "\n";

int main()
{
    int i = 0;
    CHECK( bar(i, foo)                     );
    CHECK( bar(1.0, foo)                   );
    CHECK( bar(1.0f, foo)                  );
    CHECK( bar(&i, foo)                    );
    CHECK( bar("abc", foo)                 );
    CHECK( bar(std::string("abc"), foo)    );
    CHECK( bar(foo, foo)                   );
}

Ответы на вопрос(1)

Ваш ответ на вопрос