Łukasz Wojakowski
Łukasz Wojakowski

Reputation: 134

How to have ADL prefer a function template to another

I was wondering if it is possible to have ADL select the function template defined in the namespace of the class of one of the arguments (or in some other well defined place) in a situation when other function templates are visible. I have a motivating example that follows, and although I know the way around for that particular case (I discuss it below), the question in general seems to make sense.

I thought kind of cool to avoid using friend declarations but rather delegate work to methods, and thus came up with

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
  }

So far, so good, both use cases work fine, but then, I added a second class with its own swap method and decided to save on boilerplate by turning the freestanding swap into a template:

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
  }

And here use case 1 breaks, the compiler complains about ambiguity with the std::swap template. If one anticipates the problem, it is possible to define swap functions rahter than methods (they will usually be friend, since they replace methods):

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

Now everything works, so in the case of swap it is just enough to remember to use friend functions rahter than methods, but how about the general case? Is there any hack, however dirty, that would let the compiler unambiguously select n::foo<a> (or some other foo<a> under our control) in a situation where other template<class T> foo are visible, either in the global namespace or because of some using clause, especially if the latter are not ours to modify?

Upvotes: 2

Views: 1166

Answers (2)

Łukasz Wojakowski
Łukasz Wojakowski

Reputation: 134

I know I must look silly to be answering my own question, but the fact of posting it, and the discussion, really brought some new understanding to me.

In retrospection, what should have struck me in the first place is the sequence

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

It's so old-hat, and it clearly must be wrong, since using it repeatedly requires one to copy-paste the algorithm (of using using and then swapping). And you should not copy-paste, even if the algorithm is a two-liner. So what can be done better about it? How about turning it into a one-liner:

stdfallback::do_swap(a1,a2);

Let me provide the code that allows this:

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);
    }
  }

In the solution you find a SFINAE-based traits class has_custom_swap whose value is true or false depending on whether an unqualified call to swap for lvalues of the instantiation type is found (for that need the lvalue template, similar to declval but resolving to l-value rather than r-value), and then two overloads of a do_swap method for the case when the custom swap is present, and when it is not. They have to be called different than swap, otherwise the one calling the unqualified custom swap does not compile, because it is itself ambiguous to the swap it tries to call.

So maybe we should consider using this pattern instead of the established using?

(To give proper credit, the traits solution was inspired by http://blog.quasardb.net/sfinae-hell-detecting-template-methods/)

Upvotes: 0

TemplateRex
TemplateRex

Reputation: 70516

The culprit here is not just that you write using std::swap, but fundamentally that you have provided your own unrestricted function template swap that will give an overload resolution error with std::swap whenever namespace std is being considered during name lookup (either by an explicit using directive, or by ADL).

To illustrate: just leaving out the using std::swap will rescue you in this case

Live On Coliru

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

But suppose that you refactor your classes a and b into class templates b<T> and b<T>, and call them with a template argument from namespace std (e.g. std::string), then you get an overload resolution error:

Live On Coliru

#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)
}

Conclusion: if you define your own version of swap with the same signature as std::swap (as far as overload resolution is concerned), always qualify calls to it in order to disable ADL.

Tip: better yet, don't be lazy, and just provide your own swap function (not function template) for each class in your own namespace.

See also this Q&A where a similar mechanism is explained for why it is a bad idea to provide your own begin and end templates and expect them to work with ADL.

Upvotes: 1

Related Questions