用于检测像(dereferencable)类型的指针的模板函数对于实际指针类型

时间:2018-04-18 16:36:09

标签: c++ templates c++17 template-meta-programming sfinae

我正在尝试编写一种机制来检测类型是否是指针,如类型。我的意思是它通过operator*()operator->()可以取消引用。

我有三种不同的结构,相应的专业:

  1. is_pointer_like_dereferencable检查operator*()
  2. is_pointer_like_arrow_dereferencable检查operator->()
  3. is_pointer_like简单地结合了1& 2
  4. 我添加了非模板类型的专精,例如int, int*, ...std::vector<...>, std::shared_ptr<...>, ...等模板类型。

    然而,在实施is_pointer_like_arrow_dereferencable时我似乎犯了一个错误。相关代码是

    template <typename T, typename = void>
    struct is_pointer_like_arrow_dereferencable : std::false_type 
    {
    };
    
    template <typename T>
    struct is_pointer_like_arrow_dereferencable<T, std::enable_if_t<
                                                    std::is_pointer_v<T> ||
                                                    std::is_same_v<decltype(std::declval<T>().operator->()), std::add_pointer_t<T>>>
        > : std::true_type
    {
    };
    
    
    template <template <typename...> typename P, typename T, typename... R>
    struct is_pointer_like_arrow_dereferencable<P<T, R...>, std::enable_if_t<
                                                    std::is_same_v<decltype(std::declval<P<T, R...>>().operator->()), std::add_pointer_t<T>>>
        > : std::true_type
    {
    };
    
    template <typename T>
    constexpr bool is_pointer_like_arrow_dereferencable_v = is_pointer_like_arrow_dereferencable<T>::value;
    

    第二个结构应检查非模板化类型是否是实际指针类型,或者类型是否具有返回指向自身的指针的箭头操作符。但是当我使用下面的代码测试机制时,指针类型(如int*未被正确检测到)。那是为什么?

    template <typename T>
    struct Test
    {
        T& operator*()
        {
            return *this;
        }
    
        T* operator->()
        {
            return this;
        }
    };   
    
    void main()
    {
        bool
            a = is_pointer_like_arrow_dereferencable_v<int>, // false
            b = is_pointer_like_arrow_dereferencable_v<int*>, // false, should be true
            c = is_pointer_like_arrow_dereferencable_v<vector<int>>, // false
            d = is_pointer_like_arrow_dereferencable_v<vector<int>*>, // false, should be true
            e = is_pointer_like_arrow_dereferencable_v<Test<int>>, // true
            f = is_pointer_like_arrow_dereferencable_v<Test<int>*>, // false, should be true
            g = is_pointer_like_arrow_dereferencable_v<shared_ptr<int>>, // true
            h = is_pointer_like_arrow_dereferencable_v<shared_ptr<int>*>, // false, should be true
            i = is_pointer_like_arrow_dereferencable_v<int***>; // false
    }
    

    is_pointer_like_dereferencable结构仅在std::is_same_v<...>部分不同,它确实正确检测实际指针类型。

    它无法检测指针类型(应由std::is_pointer_v<...>覆盖)这一事实对我没有任何意义。有人可以解释一下吗?

4 个答案:

答案 0 :(得分:3)

  

但是当我使用下面的代码测试机制时,指针类型(如int *未被正确检测)。那是为什么?

S.F.I.N.A.E。:替换失败不是错误

因此,对于int*,来自decltype(std::declval<T>().operator->(),您会遇到替换失败,并且不考虑专业化。所以使用了一般形式,所以std::false

您应该编写两个特化:一个或多个指针,一个用于启用operator->()的类。

奖励回答:而不是类型特征为is_pointer_like_arrow_dereferencable(过于复杂,恕我直言),我建议你通过一组辅助函数(仅声明)

template <typename>
std::false_type is_pointer_like (unsigned long);

template <typename T>
auto is_pointer_like (int)
   -> decltype( * std::declval<T>(), std::true_type{} );

template <typename T>
auto is_pointer_like (long)
   -> decltype( std::declval<T>().operator->(), std::true_type{} );

所以is_pointer_like_arrow_dereferencable可以简单地写成using

template <typename T>
using is_pointer_like_arrow_dereferencable = decltype(is_pointer_like<T>(0));

有助手is_pointer_like_arrow_dereferencable_v

template <typename T>
static auto const is_pointer_like_arrow_dereferencable_v
   = is_pointer_like_arrow_dereferencable<T>::value;

以下是一个完整的工作示例

#include <type_traits>
#include <iostream>
#include <memory>
#include <vector>

template <typename>
std::false_type is_pointer_like (unsigned long);

template <typename T>
auto is_pointer_like (int)
   -> decltype( * std::declval<T>(), std::true_type{} );

template <typename T>
auto is_pointer_like (long)
   -> decltype( std::declval<T>().operator->(), std::true_type{} );

template <typename T>
using is_pointer_like_arrow_dereferencable = decltype(is_pointer_like<T>(0));

template <typename T>
static auto const is_pointer_like_arrow_dereferencable_v
   = is_pointer_like_arrow_dereferencable<T>::value;


template <typename T>
struct Test
 {
   T & operator*  () { return *this; }
   T * operator-> () { return  this; }
 }; 

int main()
 {
   std::cout << is_pointer_like_arrow_dereferencable_v<int>
      << std::endl, // false
   std::cout << is_pointer_like_arrow_dereferencable_v<int*>
      << std::endl, // true
   std::cout << is_pointer_like_arrow_dereferencable_v<std::vector<int>>
      << std::endl, // false
   std::cout << is_pointer_like_arrow_dereferencable_v<std::vector<int>*>
      << std::endl, // true
   std::cout << is_pointer_like_arrow_dereferencable_v<Test<int>>
      << std::endl, // true
   std::cout << is_pointer_like_arrow_dereferencable_v<Test<int>*>
      << std::endl, // true
   std::cout << is_pointer_like_arrow_dereferencable_v<std::shared_ptr<int>>
      << std::endl, // true
   std::cout << is_pointer_like_arrow_dereferencable_v<std::shared_ptr<int>*>
      << std::endl, // true
   std::cout << is_pointer_like_arrow_dereferencable_v<int***>
      << std::endl; // true
 }

答案 1 :(得分:2)

我建议使用std::experimental::is_detected然后只需:

template <typename T> dereferencable_type = decltype(*declval<T>());
template <typename T> arrow_type = decltype(declval<T>().operator->());

template <typename T> is_dereferencable =
    std::experimental::is_detected<dereferencable_type, T>;
template <typename T> has_arrow = std::experimental::is_detected<arrow_type, T>;

然后撰写这些特征:

template <typename T>
using is_pointer_like_dereferencable = is_dereferencable<T>;

template <typename T>
is_pointer_like_arrow_dereferencable = std::disjunction<std::is_pointer<T>, has_arrow<T>>;

template <typename T>
std::is_pointer_like = std::conjunction<is_pointer_like_dereferencable<T>,
                                        is_pointer_like_arrow_dereferencable<T>>;

答案 2 :(得分:1)

首先,这种专业化至多没有意义,但实际上是错误的:

template <template <typename...> typename P, typename T, typename... R>
struct is_pointer_like_arrow_dereferencable<P<T, R...>,
    std::enable_if_t<
        std::is_same_v<
            decltype(std::declval<P<T, R...>>().operator->()), 
            std::add_pointer_t<T>>>
    > : std::true_type
{ };

当涉及类似指针时,所有类型参数的类模板特化没有任何特殊之处。因此,您不应该仅仅P<T, R...>处理T,而不仅仅是template <typename T> struct is_pointer_like_arrow_dereferencable<T, std::enable_if_t< std::is_pointer_v<T> || std::is_same_v<decltype(std::declval<T>().operator->()), std::add_pointer_t<T>>> > : std::true_type { };

现在,这种专业化也存在问题:

.operator->()

首先,原始指针没有->。这使得整个布尔表达式格式错误,导致整个特化被抛出。短语发生在表达式基础上,但每个表达式仍然必须有效。

其次,add_pointer_t<T>不必返回std::vector<X>::iterator::operator->X*会返回iterator*,而不是.operator->。所以这只是检查错误的东西。

我们可以让基本情况检查指针,只需对所有类型的template <typename T, typename = void> struct is_pointer_like_arrow_dereferencable : std::is_pointer<T> { }; template <typename T> struct is_pointer_like_arrow_dereferencable<T, void_t<decltype(std::declval<T>().operator->())> > : std::true_type { }; 进行特化检查(无论它们是否为类模板特化):

is_pointer

尽管如此,int*可能还不够,因为template <typename T, typename = void> struct is_pointer_like_arrow_dereferencable : std::false_type { }; template <typename T> struct is_pointer_like_arrow_dereferencable<T*, void> : std::disjunction<std::is_class<T>, std::is_union<T>> { }; “箭头可以解除引用”是没有意义的。所以你可能想做类似的事情:

l1 = ['a','b','c','d']
l2 = ['c','d','b','a']
indices = {val: i for i, val in enumerate(l1)}

[indices[x] for x in l2]

或者其他一些。

答案 3 :(得分:0)

基于Jarod42的答案(对我而言不是编译的),我创建了以下代码段(C ++ 17),

#include "sys.h"    // Required for libcwd (debug output).
#include "debug.h"  // Required for libcwd (debug output).
#include <vector>
#include <memory>
#include <iostream>

//-----------------------------------------------------------------------------
// Start of implementation.

#include <experimental/type_traits>
#include <type_traits>

template<typename T> using dereferencable_type =
    decltype(*std::declval<T>());
template<typename T> using arrow_type =
    decltype(std::declval<T>().operator->());

template<typename T> using is_dereferencable =
    std::experimental::is_detected<dereferencable_type, T>;

template<typename T> using has_arrow =
    std::experimental::is_detected<arrow_type, T>;

template<typename T> using is_pointer_like_dereferencable =
    is_dereferencable<T>;

template<typename T> using is_pointer_like_arrow_dereferencable =
    std::disjunction<std::is_pointer<T>, has_arrow<T>>;

template <typename T> using is_pointer_like =
    std::conjunction<is_pointer_like_dereferencable<T>,
                     is_pointer_like_arrow_dereferencable<T>>;

template<typename T> inline constexpr bool is_pointer_like_dereferencable_v =
    is_pointer_like_dereferencable<T>::value;
template<typename T> inline constexpr bool is_pointer_like_arrow_dereferencable_v =
    is_pointer_like_arrow_dereferencable<T>::value;
template<typename T> inline constexpr bool is_pointer_like_v =
    is_pointer_like<T>::value;

// End of implementation
//-----------------------------------------------------------------------------

template<typename T>
struct TestNone
{
  static int const n;
};

template<typename T>
struct TestRef : public TestNone<T>
{
  T const& operator*() { return TestNone<T>::n; }
};

template<typename T>
struct TestArrow : public TestNone<T>
{
  TestArrow const* operator->() { return this; }
};

template<typename T>
struct TestBoth : virtual public TestRef<T>, virtual public TestArrow<T>
{
};

//static
template<typename T>
int const TestNone<T>::n = 42;

template<typename T>
void test()
{
  Dout(dc::notice|continued_cf, '"' <<
      libcwd::type_info_of<T>().demangled_name() << "\" is ");
  bool something = true;
  if (is_pointer_like_v<T>)
    Dout(dc::continued, "pointer-like.");
  else
  {
    if (is_pointer_like_arrow_dereferencable_v<T>)
      Dout(dc::continued, "arrow dereferencable; ");
    else
      something = is_pointer_like_dereferencable_v<T>;
    if (is_pointer_like_dereferencable_v<T>)
      Dout(dc::continued, "dereferencable; ");
  }
  if (!something)
    Dout(dc::continued, "not a pointer or pointer-like.");
  Dout(dc::finish, "");
}

int main()
{
  Debug(NAMESPACE_DEBUG::init());

  test<int>();
  test<int*>();
  test<std::vector<int>>();
  test<std::vector<int>*>();
  test<std::shared_ptr<int>>();
  test<std::shared_ptr<int>*>();
  test<std::shared_ptr<int***>>();
  test<TestNone<int>>();
  test<TestNone<int>*>();
  test<TestRef<int>>();
  test<TestRef<int>*>();
  test<TestArrow<int>>();
  test<TestArrow<int>*>();
  test<TestBoth<int>>();
  test<TestBoth<int>*>();
}

其中sys.hdebug.h是git子模块cwds的“标准”标头。

这将打印以下调试输出:

  

注意:“ int”不是指针或类似指针。
  注意:“ int*”类似于指针。
  注意:“ std::vector<int, std::allocator<int> >”不是指针或类似指针。
  注意:“ std::vector<int, std::allocator<int> >*”类似于指针。
  注意:“ std::shared_ptr<int>”类似于指针。
  注意:“ std::shared_ptr<int>*”类似于指针。
  注意:“ std::shared_ptr<int***>”类似于指针。
  注意:“ TestNone<int>”不是指针或类似指针。
  注意:“ TestNone<int>*”类似于指针。
  注意:“ TestRef<int>”是可取消引用的;
  注意:“ TestRef<int>*”类似于指针。
  注意:“ TestArrow<int>”是可取消箭头引用的;
  注意:“ TestArrow<int>*”类似于指针。
  注意:“ TestBoth<int>”类似于指针。
  注意:“ TestBoth<int>*”类似于指针。