Снова порядок определения шаблонов функций
От: Went  
Дата: 16.12.20 20:02
Оценка:
Здравствуйте. Есть код, упрощенно выглядит так (IDEONE):
#include <iostream>

template<typename T>
int f(T t)
{
    return 1;
}

template<typename T>
int g(T t)
{
  return f(t);
}

int f(float x)
{
    return 2;
}

int main() 
{
    std::cout << g(1.0f);
    return 0;
}


Проблема очевидна: шаблон функции g() не видит вторую перегрузку функции f() и при попытке вызвать g() даже там, где эта перегрузка уже определена, мы получаем ошибку.
Компилятор MSVS видит, а Apple LLVM — нет. Вроде как, второе поведение правильное. Как мне переписать структуру кода так, чтобы, не меняя порядок функций (это невозможно, так как две первые функции находятся в проекте более низкого уровня), это заработало? Я не знаток деталей стандарта, но, наверное, какая-то лазейка есть? Определить функцию f макросом не предлагать
Re: Снова порядок определения шаблонов функций
От: rg45 СССР  
Дата: 16.12.20 21:17
Оценка: 4 (1)
Здравствуйте, Went, Вы писали:

W>Проблема очевидна: шаблон функции g() не видит вторую перегрузку функции f() и при попытке вызвать g() даже там, где эта перегрузка уже определена, мы получаем ошибку.

W>Компилятор MSVS видит, а Apple LLVM — нет. Вроде как, второе поведение правильное. Как мне переписать структуру кода так, чтобы, не меняя порядок функций (это невозможно, так как две первые функции находятся в проекте более низкого уровня), это заработало? Я не знаток деталей стандарта, но, наверное, какая-то лазейка есть? Определить функцию f макросом не предлагать


Все правильно. f — это non-dependent name внутри g. Поэтому все возможные ОБЪЯВЛЕНИЯ f должны быть видны в точке вызова: "return f(t)". А те, которые не видны, просто не попадут в список кандидатов на подстановку.

Смотри: 13.8.4 Non-dependent names.


Правильные ответы:
http://rsdn.org/forum/cpp/7907067.1
Автор: watchmaker
Дата: 17.12.20

http://rsdn.org/forum/cpp/7907111.1
Автор: watchmaker
Дата: 17.12.20
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 17.12.2020 8:44 rg45 . Предыдущая версия . Еще …
Отредактировано 16.12.2020 21:54 rg45 . Предыдущая версия .
Отредактировано 16.12.2020 21:53 rg45 . Предыдущая версия .
Re: Снова порядок определения шаблонов функций
От: rg45 СССР  
Дата: 16.12.20 22:39
Оценка: 8 (2)
Здравствуйте, Went, Вы писали:

W>Как мне переписать структуру кода так, чтобы, не меняя порядок функций (это невозможно, так как две первые функции находятся в проекте более низкого уровня), это заработало?


Ты можешь заменить перегрузку специализацией: https://ideone.com/iKsxa7 — специализация подхватится нормально.

Но тут засада в том, что для функций разрешены только полные специализации, что не всегда бывает достаточно. Более гибкой кастомизации можно добиться реализовав основную шаблонную функцию f через шаблон класса, который потом уже можно будет специализировать и в хвост и в гриву: https://ideone.com/2y3jZQ
--
Не можешь достичь желаемого — пожелай достигнутого.
Re: Снова порядок определения шаблонов функций
От: kov_serg Россия  
Дата: 16.12.20 22:41
Оценка: 4 (1)
Здравствуйте, Went, Вы писали:

W>Проблема очевидна: шаблон функции g() не видит вторую перегрузку функции f() и при попытке вызвать g() даже там, где эта перегрузка уже определена, мы получаем ошибку.

W>Компилятор MSVS видит, а Apple LLVM — нет. Вроде как, второе поведение правильное. Как мне переписать структуру кода так, чтобы, не меняя порядок функций (это невозможно, так как две первые функции находятся в проекте более низкого уровня), это заработало? Я не знаток деталей стандарта, но, наверное, какая-то лазейка есть? Определить функцию f макросом не предлагать

А если так написать?
#include <iostream>

template<typename T> int f(T t) { return 1; }

template<typename T> int g(T t) { return f(t); }

template<> int f(float x) { return 2; }

int main() {
    std::cout << g(1.0f);
    return 0;
}
Re[2]: Снова порядок определения шаблонов функций
От: watchmaker  
Дата: 16.12.20 22:45
Оценка: 23 (3)
Здравствуйте, rg45, Вы писали:

R>Смотри: 13.8.4 Non-dependent names.

R>Все правильно. f — это non-dependent name внутри g. Поэтому все возможные ОБЪЯВЛЕНИЯ f должны быть видны в точке вызова.
Совсем не правильно.
Это именно что dependent name — в конструкции используется переменная t, тип которой является шаблонным параметром. То есть раз тип зависимый, то и само выражение тоже является зависимым.

Да даже в соседнем разделе этот пример приводится: https://timsong-cpp.github.io/cppwp/temp.res#general-10
В котором, во-первых, явно написано что эта конструкция является dependent, и, во-вторых, демонстрируется что это работает для вызова функции, декларация которой не была видна на момент определения шаблона (но была видна на момент инстациации).


У автора же темы очень похожий пример не работает по другой причине, хорошо описанной в https://en.cppreference.com/w/cpp/language/unqualified_lookup#Template_definition — внезапно ADL работает по разному для фундаментальных типов и для всего остального
Re: Снова порядок определения шаблонов функций
От: B0FEE664  
Дата: 16.12.20 22:46
Оценка:
Здравствуйте, Went, Вы писали:

W>Проблема очевидна: шаблон функции g() не видит вторую перегрузку функции f() и при попытке вызвать g() даже там, где эта перегрузка уже определена, мы получаем ошибку.

W>Компилятор MSVS видит, а Apple LLVM — нет. Вроде как, второе поведение правильное. Как мне переписать структуру кода так, чтобы, не меняя порядок функций (это невозможно, так как две первые функции находятся в проекте более низкого уровня), это заработало? Я не знаток деталей стандарта, но, наверное, какая-то лазейка есть? Определить функцию f макросом не предлагать

Дурное дело не хитрое, но в реальном коде так делать нельзя (скорее всего вам не подойдёт такая возможность):
#include <iostream>

template<typename T>
int f(T t)
{
  return 1;
}

template<typename T>
int g(T t)
{
  return f(t);
}


int f(float x)
{
  return 2;
}


template<>
int g<float>(float t)
{
  return f(t);
}

int main() 
{
    std::cout << g(1.0f);
    // your code goes here
    return 0;
}
И каждый день — без права на ошибку...
Re[3]: Снова порядок определения шаблонов функций
От: rg45 СССР  
Дата: 17.12.20 00:27
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>У автора же темы очень похожий пример не работает по другой причине, хорошо описанной в https://en.cppreference.com/w/cpp/language/unqualified_lookup#Template_definition — внезапно ADL работает по разному для фундаментальных типов и для всего остального


В таком случае, замена float на std::string, например, должна бы устранить проблему в примере ТС, но этого не происходит.

Кроме того, gcc отказывается компилировать такой вот код:

http://coliru.stacked-crooked.com/a/457fe599caa48919

#include <string>

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

void f(std::string) {}

int main() 
{
    g(std::string("hello"));
}


Хотя, согласно этому: https://en.cppreference.com/w/cpp/language/unqualified_lookup#Template_definition, ADL должен находить не только те функции, объявления которых видны в контексте определения шаблонной функции g, но также и те, которые видны в точке ее инстанцирования:

For a dependent name used in a template definition, the lookup is postponed until the template arguments are known, at which time ADL examines function declarations with external linkage (until C++11) that are visible from the template definition context as well as in the template instantiation context, while non-ADL lookup only examines function declarations with external linkage (until C++11) that are visible from the template definition context (in other words, adding a new function declaration after template definition does not make it visible except via ADL).


Или я что-то неправильно понял?
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[4]: Снова порядок определения шаблонов функций
От: watchmaker  
Дата: 17.12.20 00:47
Оценка: 22 (2)
Здравствуйте, rg45, Вы писали:

R>В таком случае, замена float на std::string, например, должна бы устранить проблему в примере ТС, но этого не происходит.

R>Кроме того, gcc отказывается компилировать такой вот код:
R>http://coliru.stacked-crooked.com/a/457fe599caa48919
R>
  Скрытый текст
R>#include <string>

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

R>void f(std::string) {}

R>int main() 
R>{
R>    g(std::string("hello"));
R>}
R>

Так в этом примере ADL будет искать в пространстве имён std, а функция у тебя в глобальном namespace
Надо было
  либо объявить std::f,
#include <string>

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

namespace std {  
  void f(std::string) {  }
}

int main() 
{
    g(std::string("Hello"));
}
  либо использовать тип из того-же пространства имён
#include <string>

struct my_string: std::string {
    using std::string::string;
};

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



void f(my_string) {}

int main() 
{
    g(my_string("hello"));
}




И ответ на удалённое сообщение
R>Что-то я не догнал пока, в чем именно разница для фундаментальных и для остальных. Postponed он же в любом случае, правильно? И что происходит дальше, когда типы фактических параметров становятся известны?
Дальше происходит это: https://timsong-cpp.github.io/cppwp/temp.res#temp.dep.candidate-1
Запускаются два поиска: из точки определения шаблона и ADL из точки инстанциации. В кандидаты берётся объединение найденных результатов.
То есть хотя второй поиск и отложенный, но он не совсем "обычный" (ну или не совсем полный), — в нём только ADL делается.

Проблема в том, что если все типы фундаментальные, то ADL не работает. И в результате остаётся лишь множество кандидатов из определения шаблона.

Собственно, если параметры функции не состоят целиком из фундаментальных int'oв да float'ов, то находится функции будут.

Закостылить и обойти такое поведение можно, например, обернув аргумент функции в reference-wrapper (сразу смотри demo в конце сообщения https://stackoverflow.com/a/28188355)
  Или добавив фиктивный аргумент из любого класса или перечисления

// это demo, а не пример хорошего кода

struct Dummy {};

template<typename T, typename Whatever>
int f(T, Whatever&&) {
  return 1;
}

template<typename T>
int g(T t) {
  return f(t, Dummy{});
}

int f(float, Dummy) {
  return 2;
}

// теперь вызов g(1.0f) в этом месте найдёт нешаблонную функцию f, так как с типом Dummy уже ассоциирован тот же namespace, в котором f задекларирована




Но исходную проблему из первого сообщения лучше решать, конечно, не такими костылями, а через класс с точками кастомизации (как тут предложили
Автор: rg45
Дата: 17.12.20
). Тем более, что в С++ через такие traits classes уже много что делается, и они в даже привычны. По крайней мере при взгляде на них понятно, что это предполагаемая точка для изменения поведения, а не то, что автор кода в шаблоне где-то случайно не указал fully-qualified имя
Ну и смешивать перегрузку из шаблонов и нешаблонных функций тоже не очень хорошо: как бы приоритеты выбора будут меняться от того, какие преобразования будут нужны для типа.
Отредактировано 17.12.2020 1:08 watchmaker . Предыдущая версия .
Re[2]: Снова порядок определения шаблонов функций
От: Went  
Дата: 17.12.20 05:42
Оценка: +1
Здравствуйте, rg45, Вы писали:

R>Ты можешь заменить перегрузку специализацией: https://ideone.com/iKsxa7 — специализация подхватится нормально.

R>Но тут засада в том, что для функций разрешены только полные специализации, что не всегда бывает достаточно. Более гибкой кастомизации можно добиться реализовав основную шаблонную функцию f через шаблон класса, который потом уже можно будет специализировать и в хвост и в гриву: https://ideone.com/2y3jZQ

Варианты со специализацией хороши, видимо так и придется делать, спасибо. Остается проблема в том, что в реальном коде функция f() местами определена для целых семейств с наследниками, так что на специализациях трейтов придется делать через std::is_base_of...
Re[2]: Снова порядок определения шаблонов функций
От: Went  
Дата: 17.12.20 05:44
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Дурное дело не хитрое, но в реальном коде так делать нельзя (скорее всего вам не подойдёт такая возможность):

Да, не годится. На самом деле, функций типа g() несколько, и дублировать их зоопарк для каждого случая нереально
Re[3]: Снова порядок определения шаблонов функций
От: rg45 СССР  
Дата: 17.12.20 08:17
Оценка:
Здравствуйте, Went, Вы писали:

W>Остается проблема в том, что в реальном коде функция f() местами определена для целых семейств с наследниками, так что на специализациях трейтов придется делать через std::is_base_of...


Ну так да, для этого же второй параметр и нужен. Писанины побольше получается, конечно, чем с перегрузками функций, зато возможностей больше.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 17.12.2020 8:20 rg45 . Предыдущая версия .
Re: Снова порядок определения шаблонов функций
От: Igore Россия  
Дата: 17.12.20 14:45
Оценка: +1
Здравствуйте, Went, Вы писали:

W>Как мне переписать структуру кода так, чтобы, не меняя порядок функций (это невозможно, так как две первые функции находятся в проекте более низкого уровня), это заработало? Я не знаток деталей стандарта, но, наверное, какая-то лазейка есть? Определить функцию f макросом не предлагать

А можно сделать так чтобы ввести свой forward до include этого кода?
///fwd.h
int f(float x);
template<typename T>
int f(T t);

///code
#include "fwd.h"
#include "low_code.h"

  Скрытый текст
#include <iostream>

// более коротко
int f(float x);
template<typename T>
int f(T t)
{
    return 1;
}

template<typename T>
int g(T t)
{
  return f(t);
}

int f(float x)
{
    return 2;
}

int main() 
{
    std::cout << g(1.0f);
    // your code goes here
    return 0;
}
Re[2]: Снова порядок определения шаблонов функций
От: Went  
Дата: 18.12.20 07:44
Оценка:
Здравствуйте, Igore, Вы писали:
I>А можно сделать так чтобы ввести свой forward до include этого кода?
Нет. Там перегрузки для типов, которые в корневом проекте не видны.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.