结合void_t和enable_if?

问题描述 投票:1回答:2

C++17void_t允许使用class / struct模板轻松完成SFINAE:

template <class T, class = void>
struct test {
    static constexpr auto text = "general case";
};

template <class T>
struct test<T, std::void_t<decltype(std::begin(std::declval<T>())>> {
    static constexpr auto text = "has begin iterator";
};

什么是void_t内部是一种类型。我的问题是:当void_t内部的类型特征时,如何做同样的事情。使用enable_if效果很好:

template <class T>
struct test<T, std::void_t<std::enable_if_t<std::is_class_v<T>>> {
    static constexpr auto text = "is a class";
};

有没有更短/更优雅的方式来写这个,或者“正确的方式”去做,真的要结合void_tenable_if

c++17 template-meta-programming sfinae enable-if void-t
2个回答
2
投票

std::void_t的一个重要观点是可变的

// ................VVV  <- is variadic
template <typename ...>
using void_t = void;

因此,当您必须检查多种类型时允许SFINAE工作,并且只允许其中一个类型失败时允许软故障。

如果你只需要检查一个值,你必须用std::enable_if(或类似的类型特征)检查它,我没有理由与std::void_t一起使用它。

所以,在你的例子中,“正确的方式”(恕我直言)是避免使用std::void_t

template <class T>
struct test<T, std::enable_if_t<std::is_class_v<T>>
 { static constexpr auto text = "is a class"; };

同样在使用单个decltype()的情况下,我更喜欢旧的方式(但我想这是个人品味的问题)

template <class T>
struct test<T, decltype(std::begin(std::declval<T>(), void())>
 { static constexpr auto text = "has begin iterator"; };

2
投票

你似乎不明白为什么std::void_t是必要的。我们来解决这个问题:)

在你的第一个例子中,如果你不使用std::void_t,那么部分特化将永远不会被选中,因为decltype将评估为某种类型T不是void,因此它不会与部分特化相匹配并且会回到一般情况。现在,如果您知道函数将始终返回相同类型,则可以随时更改主模板中的默认参数,但这种方式更难以更改。 (你可以看看我给另一个可能有助于理解这个问题的问题的answer)。

这就是std::void_t被引入的原因。 std::void_t只是一种身份类型特征,无论如何都是void。这意味着在你的第一个例子中,无论decltype评估的是什么,第二个模板参数将是void,因此匹配并且如果decltype格式正确则将选择特化。

std::enable_if_t是有效的,默认情况下是void,当且仅当其中的条件评估为true时。这意味着std::enable_if_t已经返回了void,无论如何,因此你不需要std::void_t将其“转换”为void,因为它已经是void

© www.soinside.com 2019 - 2024. All rights reserved.