Как сделать так, чтобы ADL предпочитал шаблон функции другому

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

Я подумал, что круто избегать использования объявлений друзей, а скорее делегировать работу методам, и поэтому придумал

namespace n
  {
  struct a
    {
    auto swap(a& a2) -> void;
    };
  auto swap(a& a1, a& a2) -> void
    {
    a1.swap(a2);
    }
  }
auto main(void) -> int
  {
  n::a a1, a2;
  using std::swap;
  swap(a1,a2);    // use case 1
  n::swap(a1,a2); // use case 2
  }

Пока все хорошо, оба варианта использования работают нормально, но затем я добавил второй класс с его собственным методом подкачки и решил сэкономить на шаблоне, превратив автономный своп в шаблон:

namespace n
  {
  struct a
    {
    auto swap(a& a2) -> void;
    };
  struct b
    {
    auto swap(b& b2) -> void;
    };
  template<class T>
  auto swap(T& t1, T& t2) -> void
    {
    t1.swap(t2);
    }
  }
auto main(void) -> int
  {
  n::a a1, a2;
  using std::swap;
  swap(a1,a2);    // use case 1
  n::swap(a1,a2); // use case 2
  }

А вот в случае использования 1 ломается, компилятор жалуется на неоднозначность с std::swap шаблон. Если кто-то предвидит проблему, можно определить swap функции, которые являются более подходящими, чем методы (они обычно будут друзьями, так как они заменяют методы):

namespace n
  {
  struct a
    {
    friend auto swap(a& a1, a& a2) -> void;
    };
  struct b
    {
    friend auto swap(b& b1, b& b2) -> void;
    };
  }

Теперь все работает, так что в случае swap Достаточно просто помнить, что использовать функции-друзья чаще, чем методы, но как насчет общего случая? Есть ли какой-нибудь хак, хоть и грязный, который позволил бы компилятору однозначно выбрать n::foo<a> (или какой-то другой foo<a> под нашим контролем) в ситуации, когда другие template<class T> foo видны либо в глобальном пространстве имен, либо из-за некоторых using пункт, особенно если последний не наш, чтобы изменить?

2 ответа

Виновник здесь не только в том, что ты пишешь using std::swap, но принципиально, что вы предоставили свой собственный шаблон неограниченной функции swap это даст ошибку разрешения перегрузки с std::swap всякий раз, когда namespace std рассматривается во время поиска имени (либо явным using директива или по ADL).

Для иллюстрации: просто опуская using std::swap спасет тебя в этом случае

Жить на Колиру

auto main() -> int
{
    n::a a1, a2;
    swap(a1,a2);    // use case 1
    n::swap(a1,a2); // use case 2
}

Но предположим, что вы реорганизуете свои занятия a а также b в шаблоны классов b<T> а также b<T> и вызвать их с аргументом шаблона из namespace std (например std::string), то вы получите ошибку разрешения перегрузки:

Жить на Колиру

#include <iostream>
#include <string>

namespace n
{

template<class>    
struct a /* as before */;

template<class>
struct b /* as before */;

}

auto main() -> int
{
    n::a<std::string> a1, a2; // oops, ADL will look into namespace std 
    swap(a1,a2);    // use case 1 (ERROR)
    n::swap(a1,a2); // use case 2 (OK)
}

Вывод: если вы определите свою собственную версию swap с той же подписью, что и std::swap (что касается разрешения перегрузки), всегда квалифицируйте вызовы к нему, чтобы отключить ADL.

Совет: еще лучше, не ленитесь, просто предоставьте свой swap функция (не шаблон функции) для каждого класса в вашем собственном пространстве имен.

См. Также этот раздел вопросов и ответов, где объясняется аналогичный механизм, почему плохая идея предоставить свой собственный begin а также end шаблоны и ожидайте, что они будут работать с ADL.

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

В ретроспективе, что должно было поразить меня в первую очередь, это последовательность

using std::swap;
swap(a1,a2);

Это так по-старому, и это, очевидно, должно быть неправильно, поскольку для его многократного использования требуется копирование-вставка алгоритма (использования using а потом обменяться). И не стоит копировать-вставлять, даже если алгоритм двухстрочный. Так что можно сделать лучше об этом? Как насчет того, чтобы превратить его в однострочник?

stdfallback::do_swap(a1,a2);

Позвольте мне предоставить код, который позволяет это:

namespace stdfallback
  {

      template<class T> 
  auto lvalue(void) -> typename std::add_lvalue_reference<T>::type;


      template <typename T>
  struct has_custom_swap
    {
        template<class Tp>
    using swap_res = decltype(swap(lvalue<Tp>(),lvalue<Tp>()));

        template <typename Tp>
    static std::true_type test(swap_res<Tp> *);

        template <typename Tp>
    static std::false_type test(...);

    static const bool value = decltype(test<T>(nullptr))::value;
    };


      template<class T>
  auto do_swap(T& t1, T& t2) -> typename std::enable_if<has_custom_swap<T>::value,void>::type
    {
    swap(t1,t2);
    }

      template<class T>
  auto do_swap(T& t1, T& t2) -> typename std::enable_if<!has_custom_swap<T>::value,void>::type
    {
    std::swap(t1,t2);
    }
  }

В решении вы найдете класс черт на основе SFINAE has_custom_swap чья value Значение true или false, в зависимости от того, найден ли неквалифицированный вызов подкачки для значений l типа экземпляра (для этого нужно lvalue шаблон, похожий на declval но преобразование в l-значение, а не в r-значение), а затем две перегрузки do_swap метод для случая, когда пользовательский своп присутствует, а когда его нет. Они должны называться иначе, чем swapв противном случае тот, который вызывает неквалифицированный пользовательский своп, не компилируется, потому что он сам по себе неоднозначен с тем свопом, который пытается вызвать.

Так что, возможно, мы должны рассмотреть возможность использования этого шаблона вместо установленного using?

(Чтобы дать должное признание, решение черт было вдохновлено http://blog.quasardb.net/sfinae-hell-detecting-template-methods/)

Другие вопросы по тегам