Здравствуйте. Есть код, упрощенно выглядит так (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 макросом не предлагать
Здравствуйте, Went, Вы писали:
W>Проблема очевидна: шаблон функции g() не видит вторую перегрузку функции f() и при попытке вызвать g() даже там, где эта перегрузка уже определена, мы получаем ошибку. W>Компилятор MSVS видит, а Apple LLVM — нет. Вроде как, второе поведение правильное. Как мне переписать структуру кода так, чтобы, не меняя порядок функций (это невозможно, так как две первые функции находятся в проекте более низкого уровня), это заработало? Я не знаток деталей стандарта, но, наверное, какая-то лазейка есть? Определить функцию f макросом не предлагать
Все правильно. f — это non-dependent name внутри g. Поэтому все возможные ОБЪЯВЛЕНИЯ f должны быть видны в точке вызова: "return f(t)". А те, которые не видны, просто не попадут в список кандидатов на подстановку.
Здравствуйте, Went, Вы писали:
W>Как мне переписать структуру кода так, чтобы, не меняя порядок функций (это невозможно, так как две первые функции находятся в проекте более низкого уровня), это заработало?
Ты можешь заменить перегрузку специализацией: https://ideone.com/iKsxa7 — специализация подхватится нормально.
Но тут засада в том, что для функций разрешены только полные специализации, что не всегда бывает достаточно. Более гибкой кастомизации можно добиться реализовав основную шаблонную функцию f через шаблон класса, который потом уже можно будет специализировать и в хвост и в гриву: https://ideone.com/2y3jZQ
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, 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;
}
Здравствуйте, 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, и, во-вторых, демонстрируется что это работает для вызова функции, декларация которой не была видна на момент определения шаблона (но была видна на момент инстациации).
Здравствуйте, 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 herereturn 0;
}
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).
Или я что-то неправильно понял?
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали: R>В таком случае, замена float на std::string, например, должна бы устранить проблему в примере ТС, но этого не происходит. R>Кроме того, gcc отказывается компилировать такой вот код: R>http://coliru.stacked-crooked.com/a/457fe599caa48919 R>
И ответ на удалённое сообщение 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 задекларирована
Но исходную проблему из первого сообщения лучше решать, конечно, не такими костылями, а через класс с точками кастомизации (как тут предложили
). Тем более, что в С++ через такие traits classes уже много что делается, и они в даже привычны. По крайней мере при взгляде на них понятно, что это предполагаемая точка для изменения поведения, а не то, что автор кода в шаблоне где-то случайно не указал fully-qualified имя
Ну и смешивать перегрузку из шаблонов и нешаблонных функций тоже не очень хорошо: как бы приоритеты выбора будут меняться от того, какие преобразования будут нужны для типа.
Здравствуйте, rg45, Вы писали:
R>Ты можешь заменить перегрузку специализацией: https://ideone.com/iKsxa7 — специализация подхватится нормально. R>Но тут засада в том, что для функций разрешены только полные специализации, что не всегда бывает достаточно. Более гибкой кастомизации можно добиться реализовав основную шаблонную функцию f через шаблон класса, который потом уже можно будет специализировать и в хвост и в гриву: https://ideone.com/2y3jZQ
Варианты со специализацией хороши, видимо так и придется делать, спасибо. Остается проблема в том, что в реальном коде функция f() местами определена для целых семейств с наследниками, так что на специализациях трейтов придется делать через std::is_base_of...
Здравствуйте, B0FEE664, Вы писали:
BFE>Дурное дело не хитрое, но в реальном коде так делать нельзя (скорее всего вам не подойдёт такая возможность):
Да, не годится. На самом деле, функций типа g() несколько, и дублировать их зоопарк для каждого случая нереально
Здравствуйте, Went, Вы писали:
W>Остается проблема в том, что в реальном коде функция f() местами определена для целых семейств с наследниками, так что на специализациях трейтов придется делать через std::is_base_of...
Ну так да, для этого же второй параметр и нужен. Писанины побольше получается, конечно, чем с перегрузками функций, зато возможностей больше.
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, Went, Вы писали: W>Как мне переписать структуру кода так, чтобы, не меняя порядок функций (это невозможно, так как две первые функции находятся в проекте более низкого уровня), это заработало? Я не знаток деталей стандарта, но, наверное, какая-то лазейка есть? Определить функцию f макросом не предлагать
А можно сделать так чтобы ввести свой forward до include этого кода?
///fwd.hint 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 herereturn 0;
}
Здравствуйте, Igore, Вы писали: I>А можно сделать так чтобы ввести свой forward до include этого кода?
Нет. Там перегрузки для типов, которые в корневом проекте не видны.