【问题标题】:C++11 std equivalent of Boost has_dereferenceBoost has_dereference 的 C++11 标准等效项
【发布时间】:2016-04-12 21:01:26
【问题描述】:

许多 Boost 的 SFINAE 助手已经出现在 C++11 的 std 库中,但 has_dereference 似乎没有。除了这个特性,我已经设法从我的包中消除了一个 Boost 依赖,我想完全摆脱它,那么如何最好地使用 C++11 标准特性来获得相同的效果?

【问题讨论】:

  • 不能只看实现吗?
  • 我不知道确切的许可证,但您可能只需将 Boost 实现复制粘贴到您的代码中。应该只是标题,不能太长。
  • @Nicol 我猜你没有? ;-) 它是通过隐蔽(且看起来很复杂)#defines 来完成的,例如 BOOST_TT_FORBIDDEN_IF,由内部 has_prefix_operator.hpp 标头使用,然后再次未定义。所以逆向工程并不容易,而且绝对不仅仅是将代码从 Boost 复制到基于标准的代码的问题。
  • 嗯,不,我真的没有,显然我猜错了。 :) this 有帮助吗?
  • @BaummitAugen 啊,这看起来很有用。我明天试试,谢谢!

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


【解决方案1】:

检查一个类是否有一些没有外部依赖的函数的最简单方法通常是使用void_t idiom。

// Define this once in your project somewhere accessible
template <class ... T>
using void_t = void;

那么诀窍总是一样的;你定义一个继承自std::false_type的类模板:

template <class T, class = void>
struct has_dereference : std::false_type {};

这是“后备”类模板。现在我们要定义一个只在类型具有我们想要的属性时才起作用的特化:

template <class T>
struct has_dereference<T, void_t<decltype(*std::declval<T>())>> : std::true_type {};

要使用,只需:

bool x = has_dereference<int*>::value;
bool y = has_dereference<int>::value;

等等

我会补充一点,从技术上讲,operator* 实际上是一个函数族;操作员既可以是 CV 资格,也可以是价值类别资格。每当您对一个类型执行检测时,您实际上是在该家族中进行检测。我不会详细介绍,因为它在实践中很少遇到(operator* 很少有值类别限定,并且运算符几乎总是有一个 const 版本,并且 volatile 很少出现)但如果你看到它是值得注意的令人惊讶的事情。

这项技术值得了解,特别是如果您正在执行没有 Boost 或 Hana 等依赖项的元编程。您可以在此处阅读有关 void_t 的更多信息:How does `void_t` work

【讨论】:

  • 效果很好。谢谢!
【解决方案2】:

这是一个简洁的小 SFINAE 特质写作助手。它使用std::void_t,如果你没有它,你可以重新实现它。

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

一旦你有了它,你的问题就很简单了。

template<class T>
using deref_result = decltype(*std::declval<T>());

template<class T>
using can_deref = can_apply<deref_result, T>;

这里的想法是隐藏std::void_t 机器。你写了一个表达“一些计算的结果”的特征,我们可以从中得到“那个计算是否有效”。

一个高度便携的void_t 看起来像:

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

一行代码会破坏一些较旧的编译器,并且两行版本也很简单。

【讨论】:

    【解决方案3】:

    Yakk的修改版:

    template <class...> struct pack {};
    
    namespace detail {
        template<template <class...> class Z, class Pack, class = void>
        struct can_apply_impl : std::false_type {};
    
        template<template<class...>class Z, class...Ts>
        struct can_apply_impl<Z, pack<Ts...>, std::void_t<Z<Ts...>> > : std::true_type {};
    }
    template<template<class...>class Z, class...Ts>
    using can_apply = detail::can_apply_impl<Z, pack<Ts...>>;
    

    DEMO

    【讨论】:

    • 啊,我打错了。我认为已修复!
    • @Yakk 确认。
    • 这并不是一个真正的新答案,它更像是对 Yakk 的评论,说他有错字?
    • @Barry 它用我的回答解决了这个问题,与我所做的(或本来应该做的)略有不同。结果是一个变体。相同的语义,不同的实现。
    猜你喜欢
    • 2011-05-26
    • 2014-01-25
    • 1970-01-01
    • 2017-11-14
    • 2013-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-22
    相关资源
    最近更新 更多