C++ 用于检测类指针(可取消引用)类型的模板函数对于实际指针类型失败

C++ 用于检测类指针(可取消引用)类型的模板函数对于实际指针类型失败,c++,templates,c++17,template-meta-programming,sfinae,C++,Templates,C++17,Template Meta Programming,Sfinae,我试图编写一种机制来检测一个类型是否是类似指针的类型。我的意思是它可以通过操作符*和操作符->去引用 我有三种不同的结构,它们相应地进行了专门化: 指针是否类似于检查运算符的解引用* 指针是否类似于箭头,它检查运算符-> 指针类似于1和2的简单组合吗 我为非模板类型(如int、int*、…)添加了专门化。。。对于std::vector、std::shared_ptr等模板化类型 然而,似乎我在实现类似于箭头的可引用性时犯了一个错误。有关守则如下: template <typename T,

我试图编写一种机制来检测一个类型是否是类似指针的类型。我的意思是它可以通过操作符*和操作符->去引用

我有三种不同的结构,它们相应地进行了专门化:

指针是否类似于检查运算符的解引用* 指针是否类似于箭头,它检查运算符-> 指针类似于1和2的简单组合吗 我为非模板类型(如int、int*、…)添加了专门化。。。对于std::vector、std::shared_ptr等模板化类型

然而,似乎我在实现类似于箭头的可引用性时犯了一个错误。有关守则如下:

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;
is_pointer_like_Dereferenceable结构仅在std::is_same_v部分不同,并且它能够正确检测实际指针类型

它无法检测std::is\u pointer\u v应该包含的指针类型,这一事实对我来说没有任何意义。有人能解释一下吗

但是当我用下面的代码测试该机制时,像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
}
S.F.I.N.A.E.:替换失败不是错误

因此,对于int*,从decltypestd::declval.operator->您会得到一个替换失败,并且不会考虑专门化。所以用的是一般形式,所以std::false

您应该编写两个专门化:一个是or指针,另一个是运算符->启用类

额外的回答:IMHO,我建议您只传递一组声明的helper函数,而不是像指针那样的类型traits,比如箭头,可取消引用的过度复杂

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{} );
与helper类似的是指针箭头

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

#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
 }
但是当我用下面的代码测试该机制时,像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
}
S.F.I.N.A.E.:替换失败不是错误

因此,对于int*,从decltypestd::declval.operator->您会得到一个替换失败,并且不会考虑专门化。所以用的是一般形式,所以std::false

您应该编写两个专门化:一个是or指针,另一个是运算符->启用类

额外的回答:IMHO,我建议您只传递一组声明的helper函数,而不是像指针那样的类型traits,比如箭头,可取消引用的过度复杂

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{} );
与helper类似的是指针箭头

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

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

首先,这种专门化充其量是毫无意义的,但实际上是错误的:

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
{ };
首先,原始指针没有.operator->。这使得整个布尔表达式的形式不正确,从而导致整个专门化被抛出。短路会逐个表达式发生,但每个表达式仍然必须有效

其次,->不必返回add\u pointer\t。std::vector::iterator::operator->返回X*,而不是迭代器*。所以这只是检查错误的东西

我们可以让基本情况检查指针,只需让专门化检查所有类型的.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
{ };

或者类似的。首先,这种专门化充其量是毫无意义的,但实际上是错误的:

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
{ };
首先,原始指针没有.operator->。这使得整个布尔表达式的形式不正确,从而导致整个专门化被抛出。短路会逐个表达式发生,但每个表达式仍然必须有效

其次,->不必返回add\u pointer\t。std::vector::iterator::operator->返回X*,而不是迭代器*。所以这只是检查错误的东西

我们可以让基本情况检查指针,只需让专门化检查所有类型的.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
{ };
或者类似的东西。

我建议使用,然后简单地拥有:

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>>;
我建议使用,然后简单地拥有:

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

基于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.h和debug.h是git子模块的“标准”头

这将打印以下调试输出:

注意:int不是指针或类似指针。 注意:int*类似于指针。 注意:std::vector不是指针或类似指针。 注意:std::vector*类似于指针。 注意:std::shared_ptr类似于指针。 注意:std::shared_ptr*类似于指针。 注意:std::shared_ptr类似于指针。 注意:TestNone不是指针或类似指针的。 注意:TestNone*类似于指针。 注意:TestRef是不可引用的; 注意:TestRef*类似于指针。 注意:TestArrow是箭头可取消引用的; 注意:TestArrow*类似于指针。 注意:testtware类似于指针。 注意:TestBoth*是po 国际米兰喜欢


基于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.h和debug.h是git子模块的“标准”头

这将打印以下调试输出:

注意:int不是指针或类似指针。 注意:int*类似于指针。 注意:std::vector不是指针或类似指针。 注意:std::vector*类似于指针。 注意:std::shared_ptr类似于指针。 注意:std::shared_ptr*类似于指针。 注意:std::shared_ptr类似于指针。 注意:TestNone不是指针或类似指针的。 注意:TestNone*类似于指针。 注意:TestRef是不可引用的; 注意:TestRef*类似于指针。 注意:TestArrow是箭头可取消引用的; 注意:TestArrow*类似于指针。 注意:testtware类似于指针。 注意:testeath*类似于指针


为什么像箭头一样的指针应该是真的?在int*上使用->意味着什么?int*确实是一个不好的例子,但是您可以使用->取消引用任何指针类型。即自动x=新标准::向量;x->向后推10;为什么像箭头一样的指针应该是真的?在int*上使用->意味着什么?int*确实是一个不好的例子,但是您可以使用->取消引用任何指针类型。即自动x=新标准::向量;x->向后推10;我明白了。相应地更改实现可以解决问题。但是,当指针类型实际上可以通过->访问时,为什么没有定义运算符->呢?因为运算符->是一个函数;decltypestd::declval.operator->给你一个函数操作符返回的类型->在T中。通过一个指向泛型结构的指针示例,你可以使用箭头操作符,但一般来说没有定义一个函数操作符->@Timo-答案得到了改进,有一种替代的方式来写is_pointer_like_arrow_dereferenced;希望这能帮上忙,这实际上是一个简洁的解决方案。但我认为你改变了is_pointer_like的语义。在我的例子中,它被定义为is\u pointer\u like=is\u pointer\u like\u可参考&&is\u pointer\u like\u arrow\u可参考。我也不明白第二个过载。为什么std::declval->运算符->?这不应该是std::declval.operator->?@Timo-很抱歉,在您的代码示例中,我看不出指针类似于取消引用;我试图改写你的例子;显然,您可以修改它以获得不同的语义。关于第二个过载。。。你说得对:是std::declval.operator->;很抱歉答对了,我明白了。相应地更改实现可以解决问题。但是,当指针类型实际上可以通过->访问时,为什么没有定义运算符->呢?因为运算符->是一个函数;decltypestd::declval.operator->给你一个函数操作符返回的类型->在T中。通过一个指向泛型结构的指针示例,你可以使用箭头操作符,但一般来说没有定义一个函数操作符->@Timo-答案得到了改进,有一种替代的方式来写is_pointer_like_arrow_dereferenced;希望这能帮上忙,这实际上是一个简洁的解决方案。但我认为你改变了is_pointer_like的语义。在我的例子中,它被定义为is\u pointer\u like=is\u pointer\u like\u可参考&&is\u pointer\u like\u arrow\u可参考。我也不明白第二个过载。为什么std::declval->运算符->?这不应该是std::declval.operator->?@Timo-很抱歉,在您的代码示例中,我看不出指针类似于取消引用;我试图改写你的例子;显然,您可以修改它以获得不同的语义。关于第二个过载。。。你说得对:是std::declval.operator->;很抱歉回答正确。我做P专门化只是为了检查返回类型,没有考虑您用迭代器指出的情况。所以我想我可以删除整个专门化。我做P专门化只是为了检查返回类型,没有考虑你用迭代器指出的情况。所以我想我可以删除整个专门化。不幸的是,我在Windows上,msvc现在似乎不支持实验性/类型特性。顺便说一句,您对arrow_类型查询运算符的定义取代了运算符->。@Timo:link提供了可能的实现。顺便说一句,拼写错误已经修复。不幸的是,我在Windows上,msvc现在似乎不支持实验性/类型特性。顺便说一句,您对arrow_类型查询运算符的定义取代了运算符->。@Timo:link提供了可能的实现。排版固定顺便说一句。