合并 void_t 和 enable_if?

Combining void_t and enable_if?

C++17 中,void_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";
};

有没有 shorter/more 优雅的写法,或者“正确的方法”,真的是结合 void_tenable_if

std::void_t 的一个重点是可变参数

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

因此当您必须检查多种类型时允许 SFINAE 工作,并允许您在只有一种类型失败时软失败。

如果你只需要检查一个值并且你必须用 std::enable_if (或类似的类型特征)来检查它,我认为没有理由将它与 [=13= 一起使用].

因此,在您的示例中,"the right way"(恕我直言)避免使用 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"; };

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

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

这就是引入 std::void_t 的原因。 std::void_t 只是一个身份类型特征,无论如何都是 void。这意味着在您的第一个示例中,无论 decltype 的计算结果是什么,第二个模板参数都将是 void,因此将匹配并且选择专业化,如果 decltype 是well-formed.

std::enable_if_t 有效,默认情况下 void 当且仅当其中的条件计算为真时。这意味着 std::enable_if_t 已经 return 是 void 无论如何,因此您不需要 std::void_t 到 "transform" 变成了void,因为已经是void.