问题 使用SFINAE检查全局运算符


我希望有几个重载,全局 to_string() 采取某种类型的函数 T 并将其转换为其字符串表示形式。对于一般情况,我希望能够写:

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();
}

我的实施 has_insertion_operator 到目前为止:

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

(它借鉴了 这个这个。) 这似乎有效。 但现在我想要一个重载版本 to_string 对于那些类型  有 operator<< 但  有自己的 to_string()  会员 功能,即:

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();
}

实施 has_to_string 是:

#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 );

(这部分似乎工作正常。它改编自 这个。) 但是,当我有:

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

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

我明白了:

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’

似乎SFINAE似乎没有发生。我该怎么写 has_insertion_operator 正确地确定它是否是全局的 operator<< 有空吗?

仅供参考:我正在使用g ++ 4.2.1(在Mac OS X上作为Xcode的一部分提供)。 另外,我希望代码只是没有第三方库的标准C ++ 03,例如Boost。

谢谢!


1560
2018-04-24 03:46


起源

这一切都是可行的,但是 为什么? - Potatoswatter
@Potatoswatter:为什么不重要。请假设在我的项目的其余部分,我知道我在做什么。如果您必须知道,它是传递任何类型的参数以构成本地化错误消息的一部分的框架的一部分。这个问题不需要的所有细节。如果您知道该怎么做,请回答这个问题。非常感谢。 - Paul J. Lucas
为什么总是很重要。 - GManNickG


答案:


我应该更加忠诚于此 这个 回答。 一个有效的实施是:

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

我相信它确实如此  实际上依赖SFINAE。


10
2018-04-24 15:04



精彩:它在C ++ 03编译器中有效吗? - Viktor Sehr
试一试,找出答案。 - Paul J. Lucas
谢谢你的代码:) - Viktor Sehr
我同意,它实际上并不是SFINAE。它只是利用重载决议+合同 operator<< 应该返回它的第一个参数(用于流插入)。这指出了一个弱点:你需要一个更通用的特征,例如: is_basic_stream_insertable<Char, CharTraits> 允许不同类型的标准流:/ +1 - sehe


初始化者 value 第48行不在SFINAE工作的背景下。尝试将表达式移动到函数声明中。

#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 );
};

但是,我不得不质疑正在进行的复杂程度。我看到非正交机制将相互磨合(to_string 与 operator<<)我听到糟糕的假设被抛出(例如 operator<< 是全局vs成员,虽然实现的代码在这方面看起来不错)。


1
2018-04-24 04:01



它没有编译有几个错误。第一个错误是:'test'没有依赖于模板参数的参数,因此必须提供'test'声明。 - Paul J. Lucas
BTW:operator <<必须是全局的,如果它是一个插入操作符,因为第一个参数必须是ostream&。 - Paul J. Lucas
BTW#2:ostream :: ostream()受到保护。 - Paul J. Lucas
@Paul:补了补丁。我想到了关于“教人钓鱼”的谚语......如果你不能调试这些东西,解决它一次就不会真正帮助你。我已经使它与GCC 4.5.1(ideone.com/Kk8rk)但是你自己将它推​​回到4.2.1。 - Potatoswatter
如果我只是自己修理它,那么任何阅读你的答案的人都会读到一个根本不起作用的答案。你真的想留下你的非工作答案吗? - Paul J. Lucas