2

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?

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • A **hack** for `swap` is to make second `T&` not deductible (as something like `identity_t`), so template in namespace would be more specialized than the standard one... – Jarod42 Dec 27 '15 at 16:44
  • I like the idea, but it does not seem to work. For the suggested `template struct identity { typedef T type; }; template using identity_t = typename identity::type;` and swap modified to `template auto swap(Type& t1, identity_t& t2) -> void` it is the `std::swap` that gets selected. – Łukasz Wojakowski Dec 27 '15 at 21:23
  • Indeed, `s::swap` is less specialized... :/ – Jarod42 Dec 27 '15 at 22:07
  • Could you clarify the core of your problem a bit more? I can see three problems being mentioned here. 1) just order the overloads based on namespace for the task's sake. 2) fix a particular problem with swap. 3) Turn friend functions into member functions as a principle (and find ways to accommodate this). E.g., is the avoidance of friend functions the only driving force in your example? – Andrzej Jan 05 '16 at 10:01
  • It came up as a swap case, but given the long established way of `using std::swap` in outer code, the solution for swap is standalone (friend) functions, I can live with that, so not 2). But doing fallback onto a foreign namespace template can be useful in other cases, and then using `using` fails if what you have specialized in your namespace is itself an unrestricted template. I was looking for that, so I guess it's your 1). And maybe a solution could influence also the recommendation for swap. This is kind of poor man's namespace concepts in some ways. I don't get your 3), what do you mean? – Łukasz Wojakowski Jan 05 '16 at 20:25

2 Answers2

1

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.

Community
  • 1
  • 1
TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • Well, you completed my question by nicely explaining the mechanisms that lead to the problem, thank you. I did not care to do that in fear of making my question too verbose. But my point in that question is not how to write `swap`, that's under control with function instead of method. This requires discipline, but it is easy to remember. The real point is, can you trick the mechanisms that you describe, by injection, inheritance from a nested namespace, whatever, you name it, so that at compile time you get the desired behaviour. Do you have any idea, or is it plain impossible? – Łukasz Wojakowski Dec 28 '15 at 21:13
  • @ŁukaszWojakowski I already told you the way: do not make `swap` a function template, just provide a plain `swap(a, a)`, and a `swap(b,b)`, and so on for every type in your `namespace n`. Alternatively, if you really hate the boilerplate, just write a variadic macro that spawns them like `PP_PROVIDE_SWAP(a, b, ...)`. – TemplateRex Dec 28 '15 at 23:31
  • You are saying that I should not use a template, while I am asking if there is any mechanism that will let me do it if I am adamant about keeping it. Swap is trivial, again, I am not asking about swap, it's just an illustration. I did my share of RTFM, and I know that what you say is the usual way (i already mentioned it in the question), but then I can't be sure if the Standard does not allow for some obscure source of cleverness, that's what I am asking about. Macros, yeah, that's the really last resort. Would like better. – Łukasz Wojakowski Dec 29 '15 at 10:59
  • @ŁukaszWojakowski I tried a number of things, in particular expression sfinae on `std::is_same_v` etc, but no matter where you put that `enable_if_t` (default template or function argument, return type), it is always including / excluding the overload set, not affecting the (equal) rank *within* the overload set. So **no**, I don't know any way to achieve what you want, but have no formal proof that it would be impoissible :) – TemplateRex Dec 29 '15 at 17:50
  • @ŁukaszWojakowski upon further standard scrutiny: the Standard provides a general template `swap` in ``, but provides *individual* overloads for each of the containers and utility classes, sometimes delegating to member-swap, sometimes to member-per-member swaps. From this I conclude, that it's not possible to provide a second general (unrestricted) template to call member swap. I think you'd have to write a proposal to have the general `swap` do a SFINAE on a member function `swap`, and a second `swap` overload that does SFINAE on a member swap not being present. – TemplateRex Dec 29 '15 at 21:17
  • Hi, I think I got what I wanted, thanks to your inspiration and to the second glass of wine... I will post it in a moment. As regards formal proofs, I deal in formal proofs for a living, and I tend to be very cautious when I see that headline, I would rather take well communicated hand-waving, makes it easier to figure it out :-) – Łukasz Wojakowski Dec 29 '15 at 21:52
0

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/)

  • can you show a working example for two user-defined classes `a` and `b`, one with and one without a member-swap? – TemplateRex Dec 29 '15 at 22:51
  • this example cannot really work. First, the `has_custom_swap` does not really work ([see here](http://coliru.stacked-crooked.com/a/b8845934bae5c348)), and even it it would, then calling it with a class template with a template argument from `namespace std`, would still bring in the general `std::swap` template and cause ambiguity. – TemplateRex Dec 29 '15 at 23:06
  • I really enjoy the motivation I get from the discussion with you, but I don't understand what does not work for you (and the downvote), see [here](http://coliru.stacked-crooked.com/a/1de4ca83688ff1f3). I take your point about having a class template instantiated with a type from `std`, there's a commented-out `static_assert` in the code that corresponds to it, but using `using` old-style would not help here, right? And I know how to build an ADL barrier here, wait for next comment. I will also have one more question where I could use some help. – Łukasz Wojakowski Dec 30 '15 at 11:16
  • Remember, I am out for hacks, I never said this should go production pronto. When you instantiate templates with classes, adl kicks in for namespaces of those classes together with their associate namespaces. But when you instantiate templates with template templates (oh, my!), only the namespaces of those template templates are considered, not their associates. So you need a wrapper, see [here](http://coliru.stacked-crooked.com/a/18e778e2d7a3a051). – Łukasz Wojakowski Dec 30 '15 at 11:25
  • Ok, sorry for spamming, but I think I get your question: you're asking if my device is able to distinguish a class in my namespace with method swap from one without method swap in the presence of an unrestricted swap template in the said namespace? The answer is no, and rightly so, if one writes an unrestricted template then the intent is that everything that is usable with this template should comply with the given concept (or have another overload), otherwise the bug is the omission, and is flagged by the compiler at use time. – Łukasz Wojakowski Dec 30 '15 at 11:41
  • The other question was about possible conflict with definitions in the namespace of the function where `swap` or the like are called. [Here](http://coliru.stacked-crooked.com/a/94909623dd13c273) is the code, but I know the answer by now: that namespace is not included by adl but by ordinary lookup, so if `using std::swap` is inside the function body, that namespace is not looked at. Hence the error in `foo` if you take out the `using` line, and that's why swap in `baz` does not work at all, with the `using` the collision is between `n::` and `std::`, without between `n::` and `corrupt::`. – Łukasz Wojakowski Dec 30 '15 at 12:42
  • Do you have a legitimate example of the usefulness of ADL going through to namespaces of template parameters? – Łukasz Wojakowski Dec 30 '15 at 12:43
  • I think the general consensus is that ADL is a little too greedy, in particular associating the namespaces of template parameters is probably too surprising for most users. – TemplateRex Dec 30 '15 at 18:25
  • In [your example](http://coliru.stacked-crooked.com/a/1de4ca83688ff1f3), the `has_custom_swap` fails for `n2::e`, even though that class has a member-swap. All I'm saying is, even if you would fix that, the `n2::` would still pull in `std::swap`. – TemplateRex Dec 30 '15 at 18:36
  • To substantiate [my earlier comment](http://stackoverflow.com/questions/34481998/how-to-have-adl-prefer-a-function-template-to-another/34519943#comment56782491_34519943), here is a [fully working example](http://coliru.stacked-crooked.com/a/b0779b8a64913135) with a `has_member_swap` trait that actually works in all cases, but where you still can't use either unqualified `swap` or a qualified `stdfallback::swap` on two variables of type `n1::c` as long as that class does have a member-swap. – TemplateRex Dec 30 '15 at 19:20
  • I don't give a damn about member swap, but I do about the freestanding function possibly coming from the unrestricted template. If a class has a member swap but no callable swap function then it does not support custom swap. So yes, as in my code, `n2::e` should be flagged as not having custom swap. And if anybody writing template classes for the specific namespace where the unrestricted template is present is worried about greedy ADL, then there's the wrapper solution for the template parameter. Ugly it is, I don't deny it, but so is `using std::swap`. Hope that about clarifies it. – Łukasz Wojakowski Dec 30 '15 at 20:54