5

I want to create an overloaded template, that runs a function Foo() if a class contains it, else it does nothing.

class A
{
public:
    template <typename U>
    void Foo(U& u)
    {
        std::cout << "Has Foo()" << std::endl;
        // Modify u
    }
};

class B
{
    // Does not contain Foo()
};

I've been trying to run it as such

template <typename T, typename U>
decltype(std::declval<T>().Foo()) TriggerFoo(T* t, U& u)
{
    t->Foo(u);
}
template <typename T, typename U>
void TriggerFoo(T* t, U& u)
{
    std::cout << "Does not have Foo()" << std::endl;
}

int main()
{
    A a;
    B b;
    U u;     // Some type

    TriggerFoo<A, U>(&a, u);    // I want to print "Has Foo()".
    TriggerFoo<B, U>(&b, u);    // Prints "Does not have Foo()".

    return 0;
}

At the moment, both classes are passed to the "Does not have Foo()" instantiation. It compiles, but obviously it doesn't work, and it is most probably because I don't understand declval well enough. I have also tried with non-template functions and it still does not work.

Any help will be greatly appreciated.

mojo1mojo2
  • 756
  • 12
  • 27
  • http://stackoverflow.com/questions/257288/is-it-possible-to-write-a-c-template-to-check-for-a-functions-existence – M.M Mar 11 '16 at 02:59
  • I appreciate the answer, it is helpful, but I don't want to have to check myself whether the function contains the type. Ideally, I would like to make it generic so that it knows which template to call, by checking if it contains Foo(), without me having to create another class/struct such as has_foo. – mojo1mojo2 Mar 11 '16 at 03:03

2 Answers2

4

There were two basic issues with your approach:

decltype(std::declval<T>().Foo())

This will never be succesfully resolved, because the Foo() in question will always take a parameter. This part should be:

decltype(std::declval<T>().Foo(std::declval<U &>()))

But now you will run into a different problem: when the class implements Foo(), the template resolution will become ambiguous. Either template function can be used.

So, you need another level of indirection, and templates of different priorities:

#include <iostream>
#include <type_traits>

class A
{
public:
    template <typename U>
    void Foo(U& u)
    {
        std::cout << "Has Foo()" << std::endl;
        // Modify u
    }
};

class B
{
    // Does not contain Foo()
};

template <typename T, typename U, typename Z=decltype(std::declval<T>().Foo(std::declval<U &>()))>
void DoTriggerFoo(T* t, U& u, int dummy)
{
    t->Foo(u);
}

template <typename T, typename U>
void DoTriggerFoo(T* t, U& u, ...)
{
    std::cout << "Does not have Foo()" << std::endl;
}

template <typename T, typename U>
void TriggerFoo(T *t, U &u)
{
    DoTriggerFoo(t, u, 0);
}

class U {};

int main()
{
    A a;
    B b;
    U u;     // Some type

    TriggerFoo<A, U>(&a, u);    // I want to print "Has Foo()".
    TriggerFoo<B, U>(&b, u);    // Prints "Does not have Foo()".

    return 0;
}

Results with gcc 5.3:

$ ./t
Has Foo()
Does not have Foo()

P.S.:

std::declval<T &>().Foo(std::declval<U &>())

It's possible that this will work better, with your actual classes.

Sam Varshavchik
  • 84,126
  • 5
  • 57
  • 106
  • Thanks so much! I had the problem of ambiguity before, and wasn't sure how to get around it. – mojo1mojo2 Mar 11 '16 at 03:35
  • Just wondering because I'm trying to get a deeper understanding of template meta-programming, could you please explain the purpose of the dummy variable? I've played around with the code, such as removing "int dummy", changing "..." to "int dummy", etc, and for the most part it becomes ambiguous. Why can't "typename Z=decltype..." work on it's own? – mojo1mojo2 Mar 11 '16 at 04:49
  • 2
    It's not just the dummy variable itself, but also the corresponding `...` parameter placeholder in the other template function. The wrapper passes a 0 for the corresponding parameter. When the decltype resolution succeeds, both template functions resolve, but the one with the `...` parameter placeholder is lower priority than the one that declares an explicit `int` parameter. This resolves the ambiguity. – Sam Varshavchik Mar 11 '16 at 11:55
  • Ah, that makes sense. Cheers. – mojo1mojo2 Mar 12 '16 at 03:46
3

Extension to Sam's answer, if you aren't using pointers you can simplify the code further which makes it look a bit neater.

#include <iostream>
#include <type_traits>

class A
{
public:
    template <typename U>
    void Foo(U& u)
    {
        std::cout << "Has Foo()\n";
    }
};

class B
{
    // Does not contain Foo()
};

template <
  typename T,
  typename U,
  typename Z=decltype(std::declval<T>().Foo(std::declval<U&>()))>
void TriggerFoo(T& t, U& u)
{
    t.Foo(u);
}

template <typename... T>
void TriggerFoo(const T&...)
{
    std::cout << "Does not have Foo()\n";
}

class U {};

int main()
{
    A a;
    B b;
    U u;

    TriggerFoo<A, U>(a, u);    // I want to print "Has Foo()".
    TriggerFoo<B, U>(b, u);    // Prints "Does not have Foo()".

    return 0;
}