是否可以使用 SFINAE/模板来检查操作员是否存在?

发布于 2024-10-07 14:17:59 字数 207 浏览 5 评论 0原文

我试图检查一个运算符在编译时是否存在,如果不存在我只想忽略它,有什么办法可以做到这一点吗?

示例运算符:

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

I'm trying to check if an operator exists at compile time, if it doesn't I just want it ignored, is there any way to do that?

example operator:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

暖阳 2024-10-14 14:17:59

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

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

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

或多或少基于 this

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 。

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

I ended up using a fallback namespace :

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

Also found the proper way to check for operators at compile time (although I'm going with the fallback namespace).

more or less based on this :

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 changed has_stream_operators a bit.

//edit removed the link, apparently the site has some attack javascript.

青朷 2024-10-14 14:17:59

这是一个老问题,但值得注意的是,Boost 刚刚为几乎所有操作员及其最新的 操作符类型特征。 OP 询问的特定运算符是使用 boost:has_left_shift 进行测试的。

This is an old question but it's worth noting that Boost just added this ability for almost all operators with their newest Operator Type Traits. The specific operator OP asked about is tested with boost:has_left_shift.

泪意 2024-10-14 14:17:59

这不太容易,在 C++03 中通常也是不可能的。例如,如果您将 int*int* 用于 op<<,您将在编译时收到硬错误。因此,对于非类类型,您需要过滤掉标准禁止的类型。

对于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);
}

It isn't too easy and in C++03 it isn't in general possible. If you use int* and int* for op<< for example, you will get a hard error at compile time. So for non-class types, you need to filter out the types that the Standard forbids.

For op+ I once wrote such a thing for the kicks. Note that I'm using C headers, because I needed to test the code with the clang compiler too, which at that time didn't support my C++ headers:

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

Of course, it's very important to do tests afterwards

// 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);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文