6

Gathering the information from Using SFINAE to check for global operator<<? and templates, decltype and non-classtypes, I got the following code:

http://ideone.com/sEQc87

Basically I combined the code from both questions to call print function if it has ostream declaration, or to call the to_string method otherwise.

Taken from question 1

namespace has_insertion_operator_impl {
  typedef char no;
  typedef char yes[2];

  struct any_t {
    template<typename T> any_t( T const& );
  };

  no operator<<( std::ostream const&, any_t const& );

  yes& test( std::ostream& );
  no test( no );

  template<typename T>
  struct has_insertion_operator {
    static std::ostream &s;
    static T const &t;
    static bool const value = sizeof( test(s << t) ) == sizeof( yes );
  };
}

template<typename T>
struct has_insertion_operator :
  has_insertion_operator_impl::has_insertion_operator<T> {
};

Taken from question 2

template <typename T>
typename std::enable_if<has_insertion_operator<T>::value, T>::type
print(T obj) {
    std::cout << "from print()" << std::endl;
}

template <typename T>
typename std::enable_if<!has_insertion_operator<T>::value, T>::type
print(T obj) {
    std::cout << obj.to_string() << std::endl;
}

Then my classes are like so:

struct Foo
{
public:
    friend std::ostream& operator<<(std::ostream & os, Foo const& foo);
};

struct Bar
{
public:
    std::string to_string() const
    {
        return "from to_string()";
    }
};

And test output:

int main()
{
    print<Foo>(Foo());
    print<Bar>(Bar());

    //print<Bar>(Foo()); doesn't compile
    //print<Foo>(Bar()); doesn't compile

    print(Foo());
    print(Bar());

    print(42);
    print('a');
    //print(std::string("Hi")); seg-fault
    //print("Hey");
    //print({1, 2, 3}); doesn't compile
    return 0;
}

The print(std::string("Hi")); line seg-faults. Can anyone tell me why?

Community
  • 1
  • 1

1 Answers1

7

Both your functions print() are supposed to return something, but instead return nothing (unlike the versions in the Q&As you linked). This is undefined behavior per paragraph 6.6.3/2 of the C++11 Standard.

If print() is not supposed to return anything, let it return void, and put the SFINAE constraint in the template parameter list:

template <typename T,
    typename std::enable_if<
        has_insertion_operator<T>::value, T>::type* = nullptr>
void print(T obj) {
    std::cout << "from print()" << std::endl;
}

template <typename T,
    typename std::enable_if<
        !has_insertion_operator<T>::value, T>::type* = nullptr>
void print(T obj) {
    std::cout << obj.to_string() << std::endl;
}

Here is a live example containing the above change.

If you are working with C++03 and cannot specify default arguments for function template parameters, just avoid specifying a type as the second template argument to std::enable_if, or specify void:

template <typename T>
typename std::enable_if<has_insertion_operator<T>::value>::type
print(T obj) {
    std::cout << "from print()" << std::endl;
}

template <typename T>
typename std::enable_if<!has_insertion_operator<T>::value>::type
print(T obj) {
    std::cout << obj.to_string() << std::endl;
}
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • Couldn't this be fixed by using the `enable_if` on a second, defaulted template parameter and returning `void`? – dyp May 13 '13 at 18:43
  • It took me a while to realize the `typename enable_if` line was the return value. –  May 13 '13 at 18:43
  • @DyP: You're reading my mind, I was just editing the answer ;) – Andy Prowl May 13 '13 at 18:43
  • @remyabel: Understandable. In fact, you could have just avoided to specify `T` as the second template argument to `std::enable_if`, which would have made the return type of `print()` to be `void()`. I edited the answer to mention that. – Andy Prowl May 13 '13 at 18:49
  • isn't it supposed to be `no test( ... );`? – BЈовић Nov 29 '13 at 07:32