Question

I have a function definition like so

template <typename T>
auto print(T t) -> decltype(t.print()) {
    return t.print();
}

The idea is that the argument must be of type T and must have the print function. This print function could return anything, explaining the need of decltype. So for example you can do:

struct Foo
{
    int print()
    {
        return 42;
    }
};

struct Bar
{
    std::string print()
    {
        return "The answer...";
    }
};

...

std::cout << print(Foo()) << std::endl;    
std::cout << print(Bar()) << std::endl;
/* outputs: 
42
The answer...
*/

I read that templates cannot do runtime instantiation and that you can have the classes derive from a base class, then determine their types to see what template argument to use. However, how would I do this for a non-class type? The idea is to be able to have:

template <typename T>
T print(T t) {
    return t;
}

as well, but this gives me ambiguous overload errors. Qualifying doesn't work, ie print<Foo>. And the other got'cha is, what if I had a functor like:

struct Foo
{
  virtual int print();
  operator int() const
  {
    return 42;
  }
};

How does it decide now?

So my question is, is it possible to resolve all these ambiguities with templates, or do I have to write a bunch of redundant code?

Tests

I incrementally added tests, copy/pasting each edit'ed solution from below. Here are the results:

With the following classes:

struct Foo
{
    int print()
    {
        return 42;
    }

    operator int() const
    {
        return 32;
    }
};

struct Bar
{
    std::string print()
    {
        return "The answer...";
    }

    operator int() const
    {
        return (int)Foo();
    }
};

struct Baz
{
    operator std::string() const
    {
        return std::string("The answer...");
    }
};

And the following test output:

std::cout << print(Foo()) << std::endl;    
std::cout << print(Bar()) << std::endl;
std::cout << print(42) << std::endl;
std::cout << print((int)Foo()) << std::endl;
std::cout << print("The answer...") << std::endl;
std::cout << print(std::string("The answer...")) << std::endl;
std::cout << print((int)Bar()) << std::endl;
std::cout << print((std::string)Baz()) << std::endl;

Both correctly output:

42
The answer...
42
32
The answer...
The answer...
32
The answer...
Was it helpful?

Solution

You could adopt the following approach, which invokes a print() member function on the input if such a member function exists, otherwise it will return the input itself:

namespace detail
{
    template<typename T, typename = void>
    struct print_helper
    {
        static T print(T t) {
            return t;
        }
    };

    template<typename T>
    struct print_helper<T, decltype(std::declval<T>().print(), (void)0)>
    {
        static auto print(T t) -> decltype(t.print()) {
            return t.print();
        }
    };
}

template<typename T>
auto print(T t) -> decltype(detail::print_helper<T>::print(t))
{
    return detail::print_helper<T>::print(t);
}

Here is a live example.

OTHER TIPS

Simple solution using manual overloading for each type which you want to print directly:

Define your first implementation which calls T::print(). Use overloading to specify alternative implementations for all types which don't have this function. I don't recommend this solution, but it's very easy to understand.

template<typename T>
auto print(T t) -> decltype(t.print()) {
    return t.print();
}

int print(int t) {
    return t;
}
std::string print(std::string t) {
    return t;
}
// ... and so on, for each type you want to support ...

More advanced solution using SFINAE which uses T::print() automatically if and only if it's there:

First, define a trait which can decide if your type has a function print(). Basically, this trait inherits from either std::true_type or std::false_type, depending on the decision being made in some helper class (_test_print). Then, use this type trait in an enable_if compile-time decision which defines only one of the two cases and hides the other one (so this is not overloading).

// Type trait "has_print" which checks if T::print() is available:
struct _test_print {
    template<class T> static auto test(T* p) -> decltype(p->print(), std::true_type());
    template<class>   static auto test(...)  -> std::false_type;
};
template<class T> struct has_print : public decltype(_test_print::test<T>(0)) {};


// Definition of print(T) if T has T::print():
template<typename T>
auto print(T t) -> typename std::enable_if<has_print<T>::value, decltype(t.print())>::type {
    return t.print();
}

// Definition of print(T) if T doesn't have T::print():
template<typename T>
auto print(T t) -> typename std::enable_if<!has_print<T>::value, T>::type {
    return t;
}

Have a look at the live demo.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top