0

I've been trying to understand SFINAE and was trying to write a simple overloaded operator << that would call the 'print' method on any class that contains such a method. I read through the answers on the question Is it possible to write a template to check for a function's existence? and tried writing:

template<class T, class = decltype(void(std::declval<T>().print), std::true_type{})>
inline std::ostream &operator<<(std::ostream &out, const T &obj) {
    obj.print(out); return out; }

and

template<class T, class = decltype(void(std::declval<T>().print(std::declval<std::ostream &>())), std::true_type{})>
inline std::ostream &operator<<(std::ostream &out, const T &obj) {
    obj.print(out); return out; }

but this simply doesn't work -- the compiler seems to have no problem instantiating the template for any type, so gives slews of 'ambiguous overload' errors when I try to print things like string literals...

Community
  • 1
  • 1
Chris Dodd
  • 119,907
  • 13
  • 134
  • 226

2 Answers2

1

Your "function exists" expression is incorrect. Try this:

template <typename T,
          typename = decltype(
               void(std::declval<T>().print(std::declval<std::ostream&>())),
                    std::true_type{})>  // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
std::ostream & operator<<(std::ostream & out, const T & obj)
{
    obj.print(out);
    return out;
}

You might also consider this alternative:

template <typename T>
auto operator<<(std::ostream & out, const T & obj)
     -> decltype(obj.print(out), (void)0, out)
{
    obj.print(out);
    return out;
}
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • The second version looks elegant but isn't there a problem if the return type of `print` has an overloaded operator `,`? I'm not sure if casting to `void` could solve this. – 5gon12eder Jan 20 '15 at 00:24
  • 1
    @5gon12eder: It probably would; feel free to cast. Or just `(obj.print(out), static_cast(0), os)`? Or `decltype(obj.print(out), throw 0, os)`. – Kerrek SB Jan 20 '15 at 00:25
  • The first one is the second (non-working) example I tried above. The second alternative seems to work, however. – Chris Dodd Jan 20 '15 at 05:37
  • @ChrisDodd: As demonstrated, both examples work. Can you reproduce the error? – Kerrek SB Jan 20 '15 at 09:06
  • After including the first decl above, a simple `std::cout << "text string";` causes g++ 4.8.2 to give the error `ambiguous overload for 'operator<<'` – Chris Dodd Jan 20 '15 at 16:48
  • I take that back -- a trivial program with just `#include `, either of my attempts, and a `cout << "string"` compiles ok. So something else (some class declaration I have in my code) is needed to make gcc trigger an error. – Chris Dodd Jan 20 '15 at 17:18
0

I don't understand what you've meant your second type parameter

class = decltype(void(std::declval<T>().print), std::true_type{})

to mean. What is this supposed to evaluate to?

I think you can make it work by using the following.

#include <iostream>
#include <type_traits>

template<typename T,
         typename = decltype(std::declval<const T>().print(std::cout))>
std::ostream&
operator<<(std::ostream& out, const T& obj)
{
  obj.print(out);
  return out;
}

struct A
{
  void
  print(std::ostream& out) const
  {
    out << "A";
  }
};

int
main()
{
  A a {};
  std::cout << "And the winner is: " << a << std::endl;
}

It will correctly output And the winner is: A but there are probably some corner cases I've overlooked.

The expression

decltype(std::declval<const T>().print(std::cout))

will evaluate to the return type of the print(std::ostream&) const member function if such function is declared and a type error otherwise.

5gon12eder
  • 24,280
  • 5
  • 45
  • 92
  • It's supposed to evalute to true type (and be ignored) if and only if the part before the comma evaluates to a valid pointer-to-method type (iff the `print` method exists) – Chris Dodd Jan 20 '15 at 16:51