3

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...
  • 1
    This is a case for [SFINAE](https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error). In a nutshell: You define your first implementation *only* for those types which have `T::print()`, and the second for all other cases. Another option is to overload your function manually with all types which you want to support but don't have a print function, like: `int print(int);` – leemes May 12 '13 at 08:48

2 Answers2

5

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.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • I think you have your conditions reversed. – Xeo May 12 '13 at 08:53
  • @Xeo: Yes, and I am going to edit anyway - I don't like this version ;) – Andy Prowl May 12 '13 at 08:55
  • Now that I have seen this construct a couple of times and still don't know how it's called, how / why it works and how to write it by myself: Can you point me to some nice documentation or tell me if it has a name? (It isn't a type trait, but it *is* SINFAE, isn't it?) – leemes May 12 '13 at 09:48
  • 1
    @leemes: Yes, it is called *expression SFINAE*. You can find more info about it for instance in [this Q&A](http://stackoverflow.com/questions/12654067/what-is-expression-sfinae) – Andy Prowl May 12 '13 at 09:50
2

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.

leemes
  • 44,967
  • 21
  • 135
  • 183
  • Going to have to go with this. This works with non-fundamental types like `string`s and `const char`. –  May 12 '13 at 08:58
  • Sorry that I mixed `` and ``; this is because I copy-pasted the checking type trait from one of my projects. It's the same, if you didn't know yet. ;) – leemes May 12 '13 at 09:02
  • 1
    @remyabel: (I did update my answer so that it works for non-fundamental types as well. Admittedly, the first version was a bad one) – Andy Prowl May 12 '13 at 09:08
  • I added some test output, so I haven't chosen an answer yet. I'm curious as to what subtle differences there are, and the outputs show. –  May 12 '13 at 09:09
  • @remyabel Andy's and my answer are essentially the same, they just use different language constructs. Mine uses SFINAE, his uses template specialization. They lead exactly to the same result. They just look and work different internally ;) – leemes May 12 '13 at 09:11
  • @remyabel: I just tried your test case with the last version of my answer and it gives the same result as leemes's solution (see [here](http://ideone.com/coQY6O)) – Andy Prowl May 12 '13 at 09:11
  • Personally (and this is only my personal taste), I like my solution more because it first defines a reusable type trait. But maybe this is because I have difficulties understanding Andy's solution. I've seen such a solution multiple times, but I understand type traits easier. – leemes May 12 '13 at 09:12
  • I will accept an answer when I can give a bounty since you both gave me quick and accurate answers. –  May 12 '13 at 09:14
  • @remyabel: At least for what concerns my answer, I don't think it deserves any bounty ;) And I do not mind if you will accept leemes's answer, because apart from personal taste for one or the other idiom, it is objectively not worse than mine. – Andy Prowl May 12 '13 at 09:46