在早期检测习语实现中使用void template参数

And*_*dyG 7 c++ language-lawyer template-meta-programming c++17

n4502中,作者描述了封装void_t技巧的检测习语的早期实现.这是它的定义以及用于定义特征的用法is_assignable(实际上它是is_copy_assignable)

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

// primary template handles all types not supporting the operation:
template< class, template<class> class, class = void_t< > >
struct
detect : std::false_type { };
// specialization recognizes/validates only types supporting the archetype:
template< class T, template<class> class Op >
struct
detect< T, Op, void_t<Op<T>> > : std::true_type { };

// archetypal expression for assignment operation:
template< class T >
using
assign_t = decltype( std::declval<T&>() = std::declval<T const &>() );

// trait corresponding to that archetype:
template< class T >
using
is_assignable = detect<void, assign_t, T>;
Run Code Online (Sandbox Code Playgroud)

他们提到他们不喜欢这个,因为voidis_assignable特质中使用:

虽然生成的代码比原始代码更容易理解,但我们不喜欢上面的检测接口,因为void 元函数调用中的参数是一个不应泄露给客户端代码的实现细节.

但是,void首先对我没有任何意义.如果我尝试使用这种类型特征来检测是否int可以复制,我可以获得std::false_type Demo.

如果我重写is_assignable为:

template< class T >
using
is_assignable = detect<T, assign_t>;
Run Code Online (Sandbox Code Playgroud)

这对我来说更有意义,然后特性似乎正常工作: 演示

所以我的问题是,我是否误解了本文档中的内容,还是仅仅是一个错字?

如果这一个错字,那么我不明白为什么作者觉得有必要讨论他们不喜欢void泄漏,这让我很确定我只是遗漏了一些东西.

And*_*dyG 1

从作者编写最终实现的方式来看is_detected,他们打算将其Op作为一个可变参数模板,允许人们表达更多概念:

(也从n4502中提取)

// primary template handles all types not supporting the archetypal Op:
template< class Default
, class // always void; supplied externally
, template<class...> class Op
, class... Args
>
struct
detector
{
  using value_t = false_type;
  using type = Default;
};
// the specialization recognizes and handles only types supporting Op:
template< class Default
, template<class...> class Op
, class... Args
>
struct
detector<Default, void_t<Op<Args...>>, Op, Args...>
{
  using value_t = true_type;
  using type = Op<Args...>;
};
//...
template< template<class...> class Op, class... Args >
using
is_detected = typename detector<void, void, Op, Args...>::value_t;
Run Code Online (Sandbox Code Playgroud)

当您进入这样的场景时, avoid变得必要,以便模板特化将在是有效表达式true_type时匹配版本。Op<Args...>

这是我对原始检测的可变参数的调整

// primary template handles all types not supporting the operation:
template< class T, template<class...> class Trait, class... TraitArgs >
struct
detect : std::false_type { };
// specialization recognizes/validates only types supporting the archetype:
template< class T, template<class...> class Trait, class... TraitArgs >
struct
detect< T, Trait, std::void_t<Trait<T, TraitArgs...>>, TraitArgs... > : std::true_type { };

template<class T, template<class...> class Trait, class... TraitArgs>
using is_detected_t = typename detect<T, Trait, void, TraitArgs...>::type; 

template<class T, template<class...> class Trait, class... TraitArgs>
constexpr bool is_detected_v = detect<T, Trait, void, TraitArgs...>::value;
Run Code Online (Sandbox Code Playgroud)

请注意,我将其重命名OpTraitArgstoTraitArgs和 using ,std::void_t这使其成为 C++17。

现在让我们定义一个特征来测试一个名为的函数, Foo该函数可以接受也可以不接受某些参数类型:

template<class T, class... Args>
using HasFoo_t = decltype( std::declval<T>().Foo(std::declval<Args>()...));
Run Code Online (Sandbox Code Playgroud)

现在我们可以得到一个类型(true_typefalse_type)给定一些T和我们的特征:

template< class T, class... Args>
using has_foo_t = is_detected_t<T, HasFoo_t, Args...>;
Run Code Online (Sandbox Code Playgroud)

最后,我们还可以“仅检查”以查看该特征对于某些提供的T和是否有效Args

template<class T, class... Args>
constexpr bool has_foo_v = is_detected_v<T, HasFoo_t, Args...>;
Run Code Online (Sandbox Code Playgroud)

这是开始测试的结构:

struct A
{
    void Foo(int)
    {
        std::cout << "A::Foo(int)\n";
    }
};
Run Code Online (Sandbox Code Playgroud)

最后是测试:

std::cout << std::boolalpha << has_foo_v<A, int> << std::endl; //true
std::cout << std::boolalpha << has_foo_v<A> << std::endl; // false
Run Code Online (Sandbox Code Playgroud)

void如果我从我的is_detected_t实现中删除is_detected_v,则选择主要专业化,并且我得到false示例)。

这是因为如果模板参数格式良好,则是void为了匹配std::void_t<Trait<T, TraitArgs...>>它的类型(如果您还记得的话) 。void如果模板参数的格式不正确,则std::void_t<Trait<T, TraitArgs...>>不是一个好的匹配,它将恢复为默认的特化 ( false_type)。

void当我们从调用中删除(并简单地保留TraitArgs...在其位置)时,我们无法匹配专业化std::void_t<Trait<T, TraitArgs...>>中的参数true_type

另请注意, ifstd::void_t<Trait<T, TraitArgs...>>格式良好,它只是为主模板中的参数提供类型voidclass... TraitArgs因此我们不需要定义额外的模板参数来接收void.

总之,作者希望删除void最终会出现在客户端代码中的内容,因此本文后面的实现会稍微复杂一些。

感谢@Rerito 指出了这个答案,Yakk 还做了一些额外的工作来避免void客户端代码中的麻烦。

  • 如果这个人想要获得这样的批准,他/她不应该对其进行校对和测试吗?对于读者来说非常烦人。 (2认同)