【问题标题】:Is it possible to use SFINAE/templates to check if an operator exists?是否可以使用 SFINAE/模板来检查运算符是否存在?
【发布时间】:2010-12-13 23:39:06
【问题描述】:

我正在尝试在编译时检查运算符是否存在,如果不存在,我只想忽略它,有什么办法吗?

示例运算符:

 template <typename T>
 QDataStream& operator<<(QDataStream& s, const QList<T>& l);

【问题讨论】:

  • boost.org/doc/libs/1_39_0/libs/type_traits/doc/html/index.html 可能会也可能不会帮助您。 :)
  • 谢谢,并没有真正帮助,但让我知道要搜索什么。
  • @OneOfOne:这是 Concepts 打算提供的信息,请查看 boost.org/doc/libs/1_45_0/libs/concept_check/concept_check.htm 以了解如何创建将检查该信息的元运算符(与 enable_if 一起使用)
  • 您在答案中提供的链接将作为 Firefox 的攻击页面出现。将您希望显示的任何代码放在实际帖子中,或使用 codepad.org 之类的东西,请不要将人们发送到嵌入了攻击代码 javascripts 的网站。
  • @MSalters:请仔细阅读评论。阅读帖子,我知道你们在荷兰做了很多底池,它可能会影响您的阅读/思考能力,所以这次我就让它飞起来。 :)

标签: c++ templates sfinae


【解决方案1】:

我最终使用了一个后备命名空间:

namespace operators_fallback {
template <typename T>
inline QDataStream& operator<<(QDataStream& s, const T &) { return s; }

template <typename T>
inline QDataStream& operator>>(QDataStream& s, T &) { return s; }

template <typename T>
inline QDebug operator<<(QDebug d, const T &) { return d; }
};

...
inline void load(QDataStream & s) {
    using namespace operator_fallback;
    s >> item;
}

还找到了在编译时检查运算符的正确方法(尽管我将使用后备命名空间)。

或多或少基于这个

namespace private_impl {
    typedef char yes;
typedef char (&no)[2];

struct anyx { template <class T> anyx(const T &); };

no operator << (const anyx &, const anyx &);
no operator >> (const anyx &, const anyx &);


template <class T> yes check(T const&);
no check(no);

template <typename StreamType, typename T>
struct has_loading_support {
    static StreamType & stream;
    static T & x;
    static const bool value = sizeof(check(stream >> x)) == sizeof(yes);
};

template <typename StreamType, typename T>
struct has_saving_support {
    static StreamType & stream;
    static T & x;
    static const bool value = sizeof(check(stream << x)) == sizeof(yes);
};

template <typename StreamType, typename T>
struct has_stream_operators {
    static const bool can_load = has_loading_support<StreamType, T>::value;
    static const bool can_save = has_saving_support<StreamType, T>::value;
    static const bool value = can_load && can_save;
};
}
template<typename T>
struct supports_qdatastream : private_impl::has_stream_operators<QDataStream, T> {};

template<typename T>
struct can_load : private_impl::has_loading_support<QDataStream, T> {};

template<typename T>
struct can_save : private_impl::has_saving_support<QDataStream, T> {};

template<typename T>
struct can_debug : private_impl::has_saving_support<QDebug, T> {};

//edit 稍微改变了 has_stream_operators。

//edit删除了链接,显然该网站有一些攻击javascript。

【讨论】:

  • 如果已经定义了运算符,会不会导致二义重载?
  • 不,因为它是一个模板,所以“真实”运算符将是首选。
  • 如果“真实”运算符也是一个模板呢?
  • 您提供的链接将作为 Firefox 的攻击页面出现。
  • @OneOfOne 在阅读了您的后备命名空间解决方案后,我有一个灵光乍现的时刻,但它对我不起作用。在 VS10 和 ideone (gcc 4.7.2) (ideone.com/qzjpcL) 上测试
【解决方案2】:

这是一个老问题,但值得注意的是,Boost 刚刚为几乎所有运营商添加了此功能,并使用了最新的Operator Type Traits。 OP询问的具体算子是用boost:has_left_shift测试的。

【讨论】:

  • 我检查了这一点,这个类中充满了与我见过并尝试过的所有其他解决方法相同的所有错误和问题。 :(
  • @AscensionSystems 你能说得更具体点吗?
  • 只需查看相关文档即可。它列出了一些已知问题,由于问题的性质,这些问题基本上使其无用。
  • 那些不是错误,它们是已知的限制。它们是否“让它变得无用”完全取决于你想用它们做什么。我个人发现has_left_shift 对于我需要它的特定任务非常有用。您知道实现 OP 在 C++ 中要求的更好的解决方案吗?
  • 是的,实际上我最终在我的应用程序中使用 cdiggins::any 来检测 做了一些诡计。不支持左移的对象会在编译时被检测到,并且生成的模板代码会更改功能以适应每种情况。
【解决方案3】:

这不太容易,在 C++03 中通常是不可能的。例如,如果将int*int* 用于op&lt;&lt;,则会在编译时出现硬错误。所以对于非类类型,你需要过滤掉标准禁止的类型。

对于op+,我曾经写过这样的东西。请注意,我使用的是 C 标头,因为我还需要使用 clang 编译器测试代码,当时它不支持我的 C++ 标头:

#include <stddef.h>
#include <stdio.h>

namespace detail {
struct any { 
  template<typename T> any(T const&); 
};
struct tag { char c[2]; };

int operator,(detail::tag, detail::tag);
template<typename T> void operator,(detail::tag, T const&);
char operator,(int, detail::tag);
}

namespace fallback {
  detail::tag operator+(detail::any const&, detail::any const&);
}

namespace detail {
template<typename T>
struct is_class {
  typedef char yes[1];
  typedef char no[2];

  template<typename U>
  static yes &check(int U::*);
  template<typename U>
  static no  &check(...);

  static bool const value = sizeof check<T>(0) == 1;
};

template<typename T>
struct is_pointer { typedef T pointee; static bool const value = false; };
template<typename T>
struct is_pointer<T*> { typedef T pointee; static bool const value = true; };

template<typename T, typename U>
struct is_same {
  static bool const value = false;
};

template<typename T>
struct is_same<T, T> {
  static bool const value = true;
};

template<typename T> 
struct is_incomplete_array {
  static bool const value = false;
};

template<typename T>
struct is_incomplete_array<T[]> {
  static bool const value = true;
};

template<typename T>
struct is_reference {
  typedef T referee;
  static bool const value = false;
};

template<typename T>
struct is_reference<T&> {
  typedef T referee;
  static bool const value = true;
};

// is_fn checks whether T is a function type
template<typename T>
struct is_fn {
  typedef char yes[1];
  typedef char no[2];

  template<typename U>
  static no &check(U(*)[1]);

  template<typename U>
  static yes &check(...);

  // T not void, not class-type, not U[], U& and T[] invalid
  // => T is function type
  static bool const value = 
    !is_same<T const volatile, void>::value && 
    !is_class<T>::value && 
    !is_incomplete_array<T>::value &&
    !is_reference<T>::value &&
    (sizeof check<T>(0) == 1);
};

template<typename T, bool = is_fn<T>::value>
struct mod_ty {
  typedef T type;
};

template<typename T>
struct mod_ty<T, true> {
  typedef T *type;
};

template<typename T>
struct mod_ty<T[], false> {
  typedef T *type;
};

template<typename T, size_t N>
struct mod_ty<T[N], false> {
  typedef T *type;
};

// Standard says about built-in +:
//
// For addition, either both operands shall have arithmetic or enumeration type,
// or one operand shall be a pointer to a completely defined object type and 
// the other shall have integral or enumeration type.

template<typename T> struct Ty; // one particular type
struct P; // pointer
struct Nc; // anything nonclass
struct A; // anything
struct Fn; // function pointer

// matches category to type
template<typename C, typename T, 
         bool = is_pointer<T>::value, 
         bool = !is_class<T>::value,
         bool = is_fn<typename is_pointer<T>::pointee>::value>
struct match {
  static bool const value = false;
};

// one particular type
template<typename T, bool P, bool Nc, bool Fn>
struct match<Ty<T const volatile>, T, P, Nc, Fn> {
  static bool const value = false;
};

// pointer
template<typename T, bool F>
struct match<P, T, true, true, F> { 
  static bool const value = true;
};

// anything nonclass
template<typename T, bool P, bool Fn>
struct match<Nc, T, P, true, Fn> {
  static bool const value = true;
};

// anything
template<typename T, bool P, bool Nc, bool Fn>
struct match<A, T, P, Nc, Fn> {
  static bool const value = true;
};

// function pointer
template<typename T>
struct match<Fn, T, true, true, true> {
  static bool const value = true;
};

// one invalid combination
template<typename A, typename B>
struct inv;

// a list of invalid combinations, terminated by B = void
template<typename A, typename B>
struct invs;

// T[] <=> T[N] => T*
// void() => void(*)() 
// T& => T
// trying to find all invalid combinations
// for built-in op+
typedef 
invs<
  inv<Ty<float const volatile>, P>,
invs<
  inv<Ty<double const volatile>, P>,
invs<
  inv<Ty<long double const volatile>, P>,
invs<
  inv<Ty<void * const volatile>, Nc>,
invs<
  inv<Ty<void const* const volatile>, Nc>,
invs<
  inv<Ty<void volatile* const volatile>, Nc>,
invs<
  inv<Ty<void const volatile* const volatile>, Nc>,
invs<
  inv<Fn, Nc>,
invs<
  inv<Ty<void const volatile>, A>,
invs<
  inv<P, P>,
void
> > > > > > > > > > invalid_list;

// match condition: only when ECond<true> is passed by specialization,
// then it will be selected.
template<bool> struct ECond;

template<typename L, typename T, typename U, typename = ECond<true> >
struct found_impl;

// this one will first modify the input types to be plain pointers
// instead of array or function types. 
template<typename L, typename T, typename U>
struct found : found_impl<L, 
                          typename mod_ty<
                            typename is_reference<T>::referee>::type, 
                          typename mod_ty<
                            typename is_reference<U>::referee>::type> 
{ };

// match was found.
template<typename F, typename B, typename R, typename T, typename U>
struct found_impl<invs<inv<F, B>, R>, T, U, 
                  ECond<(match<F, T>::value && match<B, U>::value) ||
                        (match<B, T>::value && match<F, U>::value)> > {
  static bool const value = true;
};

// recurse (notice this is less specialized than the previous specialization)
template<typename H, typename R, typename T, typename U, typename Ec>
struct found_impl< invs<H, R>, T, U, Ec > : found_impl<R, T, U> {
};

// we hit the end and found nothing
template<typename T, typename U, typename Ec>
struct found_impl< void, T, U, Ec > { 
  static bool const value = false;
};

using namespace fallback;

template<typename T, typename U, 
         bool found_invalid = found<invalid_list, T, U>::value>
struct is_addable {
  static T t;
  static U u;
  static bool const value = sizeof (detail::tag(), (t+u), detail::tag()) != 1;
};

template<typename T, typename U>
struct is_addable<T, U, true> {
  static bool const value = false;
};

}

template<typename T, typename U> struct is_addable {
  static bool const value = detail::is_addable<T, U>::value;
};

当然,事后做测试很重要

// this one can be added
struct test {
  test operator+(test) { return(*this); }
};

// this one cannot be added
struct nono { };

// this fails because of an ambiguity, because there is a comma
// operator taking a variable parameter on its left hand side.     
struct fails { fails operator+(fails); };

template<typename T>
void operator,(T const&, fails);


int main() {
  printf("%d\n", is_addable<test, test>::value);
  printf("%d\n", is_addable<int, float>::value);
  printf("%d\n", is_addable<nono, nono>::value);
  printf("%d\n", is_addable<int*, int>::value);
  printf("%d\n", is_addable<int[1], int>::value);
  printf("%d\n", is_addable<int[1], float[2]>::value);
  printf("%d\n", is_addable<int*, float*>::value);
  printf("%d\n", is_addable<void*, float>::value);
  printf("%d\n", is_addable<void, int>::value);
  printf("%d\n", is_addable<void(), int>::value);
  printf("%d\n", is_addable<int, void(**)()>::value);
  printf("%d\n", is_addable<float*&, int*&>::value);
}

【讨论】:

  • 谢谢,但请检查下面的其他答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多