2

I'm trying to understand how to select the right overloaded function template at compile-time, but the compiler is giving me a hard time. I can make it work, but I don't understand what is going on. Let me explain.

I have two structs A and B like below. One has a special function and the other a normal.

struct A
{
    void special() { std::cout << "special called.\n"; }
};

struct B
{
    void normal() { std::cout << "normal called.\n"; }
};

My intension is to have a mechanism, which at compile-time selects the right overloaded function template depending on if the special function is available. I have two functions run, which take the struct as a parameter, so they can call the appropriate function.

template<class Func, Func f> struct Sfinae {};

template <typename U>
static void run(U& u, Sfinae<void (U::*)(), &U::special>* = 0)
{
    u.special();
}

template <typename U>
static void run(U& u, ...)
{
    u.normal();
}

I've tested this with the following, with various results:

int main()
{
    A a;
    run<A>(a, 0); // works
    run<A>(a); // ERROR: ambiguous overloaded function
    run(a, 0); // ERROR: A has no member normal
    run(a); // ERROR: ambiguous overloaded function

    B b;
    run<B>(b, 0); // works
    run<B>(b); // works
    run(b, 0); // works
    run(b); // works

    return 0;
}

I'd like to use it as run(a) without any extra argument or <>. Is there something wrong with my code when this is not working?

Also, I'm interested to understand what is going on here and why this is deducing things like this, so I need to give <A> for A but not for B? I don't know what the standard says and if this is different between compilers, but at least gcc4.4.4 on Linux and gcc 4.0.1 on Mac work like I've described.

Can someone please shed some light on this? Thanks!

murrekatt
  • 5,525
  • 4
  • 34
  • 61
  • Why are you using variadic paramters for the normal version of `run`? – Zac Howland Feb 09 '11 at 19:32
  • @Zac: A variadic parameter is the worst match for overload resolution. The idea is that this overload should be selected only if there is nothing else that matches. – Bo Persson Feb 09 '11 at 21:07
  • I get that, but for what he is showing, there is no need for it. – Zac Howland Feb 10 '11 at 13:17
  • @Zac, could you please extend your comment? What I've showed is just something I tried to make simple to show the core parts which are involved and what I can see the compiler is accepting/rejecting. I'm interested to get an explanation why it is like this. – murrekatt Feb 10 '11 at 14:42
  • From what you have described, the answer @Nawaz gave is all you need. That is, you don't appear to be doing anything with the variadic parameters in the function, so there is no need for them. Just specialize the template for the types you need specialized and leave the rest generalized. – Zac Howland Feb 10 '11 at 15:10
  • @Zac: I disagree. What Mikael showed is what I'm doing and a better version, so this is what is needed. Don't overfocus on the one thing in my question, but also on what I've done and what I'm seeing. For me this is as interesting to get an explanation for. Still none. – murrekatt Feb 11 '11 at 06:22
  • @Zac: Further, just take an example where there are many like A with a special function and many like B. Would you still specialize on the types? – murrekatt Feb 11 '11 at 06:23
  • @murrekatt: Even Mikael's answer avoids the unneeded use of variadic arguments. To answer your second question: if I had a situation where there were lots of A's and lots of B's, I'd probably create an interface definition and simply call the virtual function the same way in `run` every time. – Zac Howland Feb 11 '11 at 12:32
  • @Zac: Your right. That is also possible, but under certain circumstances. In this case, there is no relation between A's and B's. And yes, Mikael's answer is doing things differently. Better even. Still you see this kind of code for SFINAE and as I said, I'm curious to understand why the compiler gives the result it gives here in the various cases. The above code works on VC7.1 at least. – murrekatt Feb 11 '11 at 15:54
  • From what you've said, there is a relation between the A's and B's: The A's all implement a `special` function, the B's all implement a `normal` function. Which is why I'd likely create either 1 single abstract base class and have them all implement a `to_be_run` function, or 2 abstract base classes (1 for `special`, the other for `normal`). In which case, the `run` function would just call one of those 2 functions. As for your code working under VC7 ... bear in mind that just because it works on a specific compiler, that does not mean it complies with the standard nor is portable. – Zac Howland Feb 11 '11 at 16:19
  • @Zac: yes, you can do things differently, but this is outside my question. And with regards to the compilers, I just mentioned two: VC7.1 which accepts this and gcc which doesn't. Hence my question. Maybe you know what the standard says? – murrekatt Feb 12 '11 at 18:37

2 Answers2

1

For this particular situation you can do this, which is very simple:

template <typename U>
static void run(U & u)
{
    u.special();
}

template <>
static void run<B>(B &u)
{
    u.normal();
}

Or, you can simply remove template, and write two overloaded functions. I agree, this doesn't solve it in more general way.

Maybe, this topic will help you finding a general solution:

Is it possible to write a template to check for a function's existence?

See Johannes's answer. :-)

Community
  • 1
  • 1
Nawaz
  • 327,095
  • 105
  • 629
  • 812
  • Thanks Nawas. I know about this, however, it might be impractical if there are many A's and B's and I'd like to have code which works when things change as well. Say I add or remove B's or A's. Thanks also for the link. While I do want to solve what I'm trying to achieve, I'm also interested to understand that particular thing I've described. E.g. why is it like this? – murrekatt Feb 10 '11 at 07:15
1

This here will work. It sort-of assumes that the two functions normal and special are mutually exclusive (i.e. a class that has one of them doesn't have the other). I'm sure you can adapt it to your purpose. This uses boost::enable_if, of course.

#include <iostream>
#include <boost/utility/enable_if.hpp>

struct A
{
    void special() { std::cout << "special called.\n"; }
};

struct B
{
    void normal() { std::cout << "normal called.\n"; }
};

template<int> struct Sfinae { enum { value = true }; };

template <typename U>
static typename boost::enable_if<Sfinae<sizeof(&U::special)>,void>::type run(U& u)
{
    u.special();
}

template <typename U>
static typename boost::enable_if<Sfinae<sizeof(&U::normal)>,void>::type run(U& u)
{
    u.normal();
}


int main()
{
    A a;
    run(a); // works

    B b;
    run(b); // works

    return 0;
}

This works on gcc 4.6.0 on Linux.

Mikael Persson
  • 16,908
  • 6
  • 34
  • 49
  • Thanks Mikael. This solves the problem, but I'm also interested to understand what I was seeing with the code I have. For instance, I thought ... is the last to match when deducing. Here it seems to not be the case if there is a default argument and no argument is passed to the function. I don't understand this and I don't know if this is expected behavior (standard). Any ideas about this? – murrekatt Feb 10 '11 at 07:20