mojo1mojo2
mojo1mojo2

Reputation: 1130

C++ Decltype for function template

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.

Upvotes: 5

Views: 4687

Answers (2)

user12161904
user12161904

Reputation:

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

Upvotes: 5

Sam Varshavchik
Sam Varshavchik

Reputation: 118340

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.

Upvotes: 4

Related Questions