11

I want to have several overloaded, global to_string() functions that take some type T and convert it to its string representation. For the general case, I want to be able to write:

template<typename T,class OutputStringType> inline
typename enable_if<!std::is_pointer<T>::value
                && has_insertion_operator<T>::value,
                   void>::type
to_string( T const &t, OutputStringType *out ) {
  std::ostringstream o;
  o << t;
  *out = o.str();
}

My implementation of has_insertion_operator so far is:

struct sfinae_base {
  typedef char yes[1];
  typedef char no[2];
};

template<typename T>
struct has_insertion_operator : sfinae_base {
  template<typename U> static yes& test( U& );
  template<typename U> static no& test(...);

  static std::ostream &s;
  static T const &t;

  static bool const value = sizeof( test( s << t ) ) == sizeof( yes ); // line 48
};

(It borrows from this and this.) That seems to work. But now I want to have an overloaded version of to_string for types that do not have operator<< but do have their own to_string() member function, i.e.:

template<class T,class OutputStringType> inline
typename enable_if<!has_insertion_operator<T>::value
                && has_to_string<T,std::string (T::*)() const>::value,
                   void>::type
to_string( T const &t, OutputStringType *out ) {
  *out = t.to_string();
}

The implementation of has_to_string is:

#define DECL_HAS_MEM_FN(FN_NAME)                                      \
  template<typename T,typename S>                                     \
  struct has_##FN_NAME : sfinae_base {                                \
    template<typename SignatureType,SignatureType> struct type_check; \
    template<class U> static yes& test(type_check<S,&U::FN_NAME>*);   \
    template<class U> static no& test(...);                           \
    static bool const value = sizeof( test<T>(0) ) == sizeof( yes );  \
  }

DECL_HAS_MEM_FN( to_string );

(This part seems to work fine. It's adapted from this.) However, when I have:

struct S {
  string to_string() const {
    return "42";
  }
};

int main() {
  string buf;
  S s;
  to_string( s, &buf ); // line 104
}

I get:

foo.cpp: In instantiation of ‘const bool has_insertion_operator<S>::value’:
foo.cpp:104:   instantiated from here
foo.cpp:48: error: no match for ‘operator<<’ in ‘has_insertion_operator<S>::s << has_insertion_operator<S>::t’

It seems like SFINAE is not happening. How do I write has_insertion_operator correctly such that it determines whether a global operator<< is available?

FYI: I'm using g++ 4.2.1 (that which ships as part of Xcode on Mac OS X). Also, I'd like the code to be only standard C++03 without 3rd-party libraries, e.g., Boost.

Thanks!

Community
  • 1
  • 1
Paul J. Lucas
  • 6,511
  • 5
  • 41
  • 77
  • 1
    It's all certainly doable, but *why*? – Potatoswatter Apr 24 '11 at 03:49
  • 2
    @Potatoswatter: the why isn't important. Please assume that for the rest of my project that I know what I'm doing. If you must know, it's part of framework for passing parameters of any type to form part of a localized error message. The details of all that are unnecessary for this question. If you know how to do it, please answer the question. It would be much appreciated. – Paul J. Lucas Apr 24 '11 at 03:53
  • 2
    Why is always important. – GManNickG Apr 24 '11 at 06:47

2 Answers2

9

I should have simply been more faithful to this answer. A working implementation is:

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

I believe that it does not actually rely on SFINAE.

Community
  • 1
  • 1
Paul J. Lucas
  • 6,511
  • 5
  • 41
  • 77
  • I agree, it doesn't actually SFINAE. It just leverages overload resolution + the contract that `operator<` to allow for different types of standard streams :/ +1 – sehe Sep 04 '13 at 07:44
1

The initializer of value on line 48 is not in a context where SFINAE works. Try moving the expression to the function declaration.

#include <iostream>

struct sfinae_base {
  typedef char yes[1];
  typedef char no[2];
};

template<typename T>
struct has_insertion_operator : sfinae_base {

  // this may quietly fail:
  template<typename U> static yes& test(
      size_t (*n)[ sizeof( std::cout << * static_cast<U*>(0) ) ] );

  // "..." provides fallback in case above fails
  template<typename U> static no& test(...);

  static bool const value = sizeof( test<T>( NULL ) ) == sizeof( yes );
};

However, I have to question the amount of sophistication that is going into this. I see non-orthogonal mechanisms that will grind against each other (to_string vs. operator<<) and I hear poor assumptions getting tossed around (for example that operator<< is global vs a member, although the code as implemented looks OK in that regard).

Potatoswatter
  • 126,977
  • 21
  • 238
  • 404
  • 1
    It doesn't compile with several errors. The first error is: there are no arguments to ‘test’ that depend on a template parameter, so a declaration of ‘test’ must be available. – Paul J. Lucas Apr 24 '11 at 04:16
  • BTW: operator<< must be global if it's to be an insertion operator since the first argument must be an ostream&. – Paul J. Lucas Apr 24 '11 at 04:18
  • BTW#2: ostream::ostream() is protected. – Paul J. Lucas Apr 24 '11 at 04:22
  • @Paul: Patched up. The adage about "teach a man to fish" comes to mind… if you can't debug these things, solving it once won't really help you. I've made it work with GCC 4.5.1 (http://ideone.com/Kk8rk) but you're on your own backporting it to 4.2.1. – Potatoswatter Apr 24 '11 at 04:25
  • 3
    Had I simply fixed it myself, then anybody else who read your answer would have read an answer that simply doesn't work. Do you really want to leave your non-working answers as-is? – Paul J. Lucas Apr 24 '11 at 04:30
  • @Potatoswatter: The other thing that's wrong is that your answer assumes that T has a default constructor. If it doesn't, it will result in a false negative. – Paul J. Lucas Apr 24 '11 at 05:40
  • @Potato, which endeavor is that? The endeavor of maintaining quality questions on SO? – Kirk Woll Apr 24 '11 at 05:47
  • You should de-reference a pointer- this code will fail if `U()` is not default-constructible, which is not part of the stated intention. – Puppy Apr 24 '11 at 06:10
  • @Kirk: I don't think this page is very high quality one way or another, because this is probably not the right sort of approach to the actual problem. These SFINAE tricks are mostly good for expression templates and pure introspection, which doesn't seem to be what's happening here. It doesn't work well on moderately old compilers without a lot of special cases. I think if Paul were more familiar with the repertoire of tools (and if he were, he wouldn't need to ask this question), he wouldn't choose SFINAE. – Potatoswatter Apr 24 '11 at 06:15
  • @Potatoswatter: if you don't like the question and "simply don't care" why not save yourself the entire bother and refrain from posting anything at all? – Paul J. Lucas Apr 24 '11 at 07:30
  • @Paul: To me, the valuable part of the answer is the comments which explain the "missing piece" of how SFINAE works. That part I do care about, and it's essentially the only change I made to your code at first. In any case, good luck making your system work. – Potatoswatter Apr 24 '11 at 07:37