如何检查下标运算符的存在?

时间:2015-07-09 00:00:27

标签: c++ templates c++11 sfinae typetraits

我想编写一个类型特征,它使用SFINAE来检查类型是否存在下标表达式。下面的初始尝试似乎在下标表达式可行时起作用,但在括号运算符不存在时不起作用。

#include <iostream>
#include <vector>
#include <cassert>

template<class T, class Index>
struct has_subscript_operator_impl
{
  template<class T1,
           class Reference = decltype(
             (*std::declval<T*>())[std::declval<Index>()]
           ),
           class = typename std::enable_if<
             !std::is_void<Reference>::value
           >::type>
  static std::true_type test(int);

  template<class>
  static std::false_type test(...);

  using type = decltype(test<T>(0));
};


template<class T, class Index>
using has_subscript_operator = typename has_subscript_operator_impl<T,Index>::type;

struct doesnt_have_it {};

struct returns_void
{
  void operator[](int) {}
};

struct returns_int
{
  int operator[](int) { return 0; }
};

int main()
{
  std::cout << "has_subscript_operator<doesnt_have_it,int>: " << has_subscript_operator<doesnt_have_it,int>::value << std::endl;
  assert((!has_subscript_operator<doesnt_have_it,int>::value));

  std::cout << "has_subscript_operator<returns_void,int>: " << has_subscript_operator<returns_void,int>::value << std::endl;
  assert((!has_subscript_operator<returns_void,int>::value));

  std::cout << "has_subscript_operator<returns_int,int>: " << has_subscript_operator<returns_int,int>::value << std::endl;
  assert((has_subscript_operator<returns_int,int>::value));

  std::cout << "has_subscript_operator<int*,int>: " << has_subscript_operator<int*,int>::value << std::endl;
  assert((has_subscript_operator<int*,int>::value));

  std::cout << "has_subscript_operator<std::vector<int>,int>: " << has_subscript_operator<std::vector<int>,int>::value << std::endl;
  assert((has_subscript_operator<returns_int,int>::value));

  return 0;
}

clang-3.4的输出:

$ clang -std=c++11 -I. -lstdc++ test_has_subscript_operator.cpp 
test_has_subscript_operator.cpp:10:14: error: type 'doesnt_have_it' does not provide a subscript operator
             (*std::declval<T*>())[std::declval<Index>()]
             ^~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
test_has_subscript_operator.cpp:25:1: note: in instantiation of template class 'has_subscript_operator_impl<doesnt_have_it, int>' requested here
using has_subscript_operator = typename has_subscript_operator_impl<T,Index>::type;
^
test_has_subscript_operator.cpp:41:66: note: in instantiation of template type alias 'has_subscript_operator' requested here
  std::cout << "has_subscript_operator<doesnt_have_it,int>: " << has_subscript_operator<doesnt_have_it,int>::value << std::endl;
                                                                 ^
1 error generated.

如何修复我的has_subscript_operator以使其适用于所有类型?

2 个答案:

答案 0 :(得分:7)

SFINAE仅在直接上下文中发生替换失败时起作用。模板参数Index在成员函数模板test被实例化时已经知道,因此代替替换失败会导致硬错误。

解决此问题的方法是通过向Index添加其他模板类型参数并将其默认为test来再次推断Index

template<class T1,
       class IndexDeduced = Index,  // <--- here
       class Reference = decltype(
         (*std::declval<T*>())[std::declval<IndexDeduced>()] // and use that here
       ),
       class = typename std::enable_if<
         !std::is_void<Reference>::value
       >::type>
static std::true_type test(int);

现在您的代码按预期工作。

Live demo

答案 1 :(得分:3)

一旦你拥有C ++ 11,编写类型特征就会轻松得多......你不需要使用省略号重载技巧。您可以在魔法的帮助下直接使用decltype表达式:

template <typename... >
using void_t = void;

我们有基本情况:

template<class T, class Index, typename = void>
struct has_subscript_operator : std::false_type { };

我们的表达SFINAE有效案例:

template<class T, class Index>
struct has_subscript_operator<T, Index, void_t<
    decltype(std::declval<T>()[std::declval<Index>()])
>> : std::true_type { };

然后你可以写相同的别名:

template <class T, class Index>
using has_subscript_operator_t = typename has_subscript_operator<T, Index>::type;

你也可以使用@Yakk最喜欢的方法,给出了他在每个答案中复制的样板:

namespace details {
  template<class...>struct voider{using type=void;};
  template<class...Ts>using void_t=typename voider<Ts...>::type;

  template<template<class...>class Z, class, class...Ts>
  struct can_apply:
    std::false_type
  {};
  template<template<class...>class Z, class...Ts>
  struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:
    std::true_type
  {};
}
template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z,void,Ts...>;

然后您可以简单地写属性:

template <class T, class Index>
using subscript_t = decltype(std::declval<T>()[std::declval<Index>()]);

template <class T, class Index>
using has_subscript = can_apply<subscript_t, T, Index>;