Два кандидата функциональных шаблонов. После ссылки на один аргумент выбирается менее специализированный шаблон - PullRequest
1 голос
/ 13 января 2020

У меня есть общий код - алгоритм Дейкстры - я использую в разных контекстах, поэтому я решил использовать диспетчеризацию тегов.

Общий код находится в следующей функции (вы можете видеть, что End отправляется в зависимости от параметр шаблона тега):

template <typename Tag, typename ... Args>
void Dijkstra(blahblah, Args&&... arg) {
...
    if (End(Tag(), cost, n_id, distances, time_limit, args ...)) {
            break;
    }

Для большинства контекстов я определяю no-op по умолчанию следующим образом:

template<typename ... Args>
bool inline End(Args&& ...) {
    return false;
}

Для одного контекста я определяю функцию со следующей сигнатурой :

bool inline End(OneContextTag, Duration d, NodeId n_id, Distances distances, Du time_limit, blahblah) {

Все работало, как и ожидалось, пока я не обнаружил, что я забыл & в подписи после Distances - я каждый раз копировал Расстояния, большую неупорядоченную_карту.

Однако после того, как я изменил его на const Distances&, чтобы избежать дорогостоящего копирования, была вызвана менее специализированная версия n oop. Понятия не имею почему. И как это исправить.

(клянусь, изменение заключается только в добавлении одного символа &. Или const&)

(Подпись верна, если я закомментирую версия generi c n oop, она просто использует версию OneContextTag.

(код более сложный, но я надеюсь, что из этого можно разобраться.)

Ответы [ 2 ]

3 голосов
/ 13 января 2020

Итак, вы спрашиваете, в основном, почему следующая программа печатает Special foo, но Generic bar:

struct A {};

template<class ... Args>
void foo(Args&&...)
{
    std::cout << "Generic foo\n";
}

void foo(A)
{
    std::cout << "Special foo\n";
}

template<class ... Args>
void bar(Args&&...)
{
    std::cout << "Generic bar\n";
}

void bar(A const&)
{
    std::cout << "Special bar\n";
}

int main()
{
    A a;
    foo(a);
    bar(a);
}

Давайте посмотрим, что происходит для разрешения перегрузки:

1 , Выбираются функции-кандидаты.

C++11/[over.match.funcs]/7 В каждом случае, когда кандидат является шаблоном функции, специализации шаблона-кандидата генерируются с использованием вывода аргумента шаблона (14.8.3, 14.8.2). Затем эти кандидаты обрабатываются как функции-кандидаты обычным способом.

Кандидаты на вызов foo(a):

template<> void foo<A&>(A&); // reference collapsing
void foo(A);

Кандидаты на вызов bar(a):

template<> void bar<A&>(A&); 
void bar(A const&);

2. Выбор наилучшей жизнеспособной функции:

Во-первых, перегрузка лучше, если (по крайней мере) один из параметров имеет лучшую последовательность преобразования (и ни один другой не имеет худшую последовательность преобразования).

C++11/[over.ics.rank]/3 Стандартная последовательность преобразования S1 является лучшей последовательностью преобразования, чем стандартная последовательность преобразования S2, если [...] S1 и S2 являются ссылочными привязками (8.5.3), и типы, на которые ссылаются ссылки, являются одним и тем же типом, за исключением cv-квалификаторов верхнего уровня, и тип, на который ссылается ссылка, инициализированная S2, является более квалифицированным для cv , чем тип, на который ссылается ссылка инициализируется ссылкой S1.

Это приводит к предпочтению шаблона-кандидата для bar, поскольку преобразование, требуемое для вызова void bar(A const&), требует связывания lvalue с более квалифицированной cv ссылкой const lvalue. Таким образом, вы видите версию c generi, вызываемую при использовании Distances const&.

C++11/[over.best.ics]/6 Когда тип параметра не является ссылкой [...]
Когда параметр имеет тип класса, а выражение аргумента имеет тот же тип, последовательность неявного преобразования является преобразованием идентичности.

Это делает последовательность преобразования для параметра a при передаче в void foo(A) преобразование идентификатора (что также имеет место для функции шаблона).

Если ни одна из перегрузок не имеет лучшей последовательности преобразования, то версия без шаблона выигрывает у шаблона.

C++11/[over.match.best]/1 [...] С учетом этих определений, жизнеспособная функция F1 определяется как лучшая функция, чем другая жизнеспособная функция F2, если [...] F1 является не шаблонной функцией, а F2 является функцией специализация шаблона.

Это относится к foo и заставляет ваш код работать так, как вы предполагали, когда вы используете Distances distances.

0 голосов
/ 13 января 2020

У меня нет ответа на вопрос, почему разрешение перегрузки работает так, как здесь. Но у меня есть потенциальное решение для вас, которое также (IMO) более надежное:

Измените значение по умолчанию End, чтобы принять тег UseDefaultEnd в качестве первого параметра. Для каждого контекста, который должен использовать значение по умолчанию End, подкласс его тега из UseDefaultEnd:

#include <iostream>

struct UseDefaultEnd {};

/* Comment first parameter; then you get the same behavior as 
   you're currently trying to solve. */
template<typename ... Args>
bool inline End(UseDefaultEnd, Args&& ...) {
// bool inline End(Args&& ...) {
    return false;
}

struct OneTag {};

struct OtherTag : public UseDefaultEnd {};

bool inline End(OneTag, int const & i) {
    return true;
}

template<typename Tag>
void Caller() {
    int i = 42;
    if (End(Tag(), i)) {
        std::cout << "Used specific version of End" << std::endl;
    }
}


int main() {
    Caller<OtherTag>();
    std::cout << "---" << std::endl;
    Caller<OneTag>();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...