【问题标题】:A variable template that is true iff a class template would instantiate?如果类模板将实例化,则变量模板为真?
【发布时间】:2021-02-15 13:28:39
【问题描述】:

假设我有一个类模板 A,它有一个类型模板参数和一个主要特化:

 template<typename T> struct A {
    /*...*/
 };

对于某些T 参数A&lt;T&gt; 将成功实例化,而对于另一些则不会。

不修改或扩展A的定义,是否可以写一个bool变量模板:

template<typename T>
constexpr bool WorksWithA = /*...*/;

如果A&lt;T&gt; 实例化成功,那么WorkWithA&lt;T&gt; 为真?

更新

将此作为单独的问题发布:Using typename in C++20 requires / concept?

#include <iostream>

template<typename T>
struct A {
    using X = typename T::X;
};

template<typename T>
constexpr bool WorksWithA = requires { typename A<T>; };

struct GoodArg {
    using X = int;
};

struct BadArg {
};

int main() {
    std::cout << WorksWithA<GoodArg> << std::endl;
    std::cout << WorksWithA<BadArg> << std::endl;
}

编译并运行:

$ clang++ --version
clang version 10.0.0-4ubuntu1 
$ clang++ test.cc -std=c++20
$ ./a.out 
1
1

这输出1 1,预期输出是1 0

什么给了?

【问题讨论】:

  • 听起来你可能正在寻找std::is_constructible
  • @NathanOliver:你能把模板实例化和对象构造混为一谈吗?
  • 我认为没有办法检测实例化中的硬错误。因此,如果 A 对 SFINAE 不友好,我认为您不能自动拥有您的 WorksWithA
  • @rustyx:对,如果 A 的定义被修改,我希望 WorksWithA(以及任何相关的机器)继续给出正确的答案而不需要修改。
  • @newbie 实例化A 产生一个类型,对象构造构造一个对象

标签: c++ c++20


【解决方案1】:

如果我理解正确,对于 SFINAE 友好类型,这可能是一种方式:

如果可以实例化则匹配:

template<class T>
constexpr auto WorksWithA(int) -> A<T>;

如果不是则匹配:

struct no {};
template<class T>
constexpr auto WorksWithA(long) -> no;

助手:

template<typename T>
inline constexpr bool WorksWithA_v = 
    not std::is_same_v<no, decltype(WorksWithA<T>(0))>;

Demo


如果 A 对 SFINAE 不友好(如更新问题中的 A),您必须在 WorksWithA 中添加检查。在这种情况下:

template<typename T>
struct A {
    using X = typename T::X;
};

template<class T>
constexpr auto WorksWithA(int) -> typename T::X;

struct no {};
template<class T>
constexpr auto WorksWithA(long) -> no;

template<typename T>
inline constexpr bool WorksWithA_v =
    not std::is_same_v<no, decltype(WorksWithA<T>(0))>;

Demo

【讨论】:

  • 你也可以用no代替std::false_type
  • @bitmask 确实如此。将修复
  • does not seem to work(除非我弄错了)。
  • @rustyx A&lt;Bah&gt; bah; 失败,因为缺少默认构造函数。只有构造函数失败,A&lt;Bah&gt; 的其余部分都很好
  • 假类型是个坏主意:如果 X 是合法的假类型怎么办?
【解决方案2】:

您误解了模板的工作原理。

template<typename T>
struct A {
    using X = typename T::X;
};

template<typename T>
constexpr bool WorksWithA = requires { typename A<T>; };

struct GoodArg {
    using X = int;
};

struct BadArg {
};

BadArg 基本上与 A 一起工作。还不错。

template<typename T>
struct A {
    using X = typename T::X;
};

不要求T 有一个类型名X

这表明如果它确实有一个类型名X,那么A::X就是那个类型名。

所以如果T 可以实例化模板AWorksWithA 会正确测试。

这对您没有直接帮助,但了解您提出了错误的问题以及为什么您的问题是错误的问题很重要。因为这会引出正确的问题。

或者T没有X类型时,你想要A不实例化,或者你想要WorksWithA做不同的类型测试。

例如,也许你想要HasAnX:

template<class T>
concept HasAnX = requires { typename T::X; };

然后

static_assert(HasAnX<GoodArg>);
static_assert(!HasAnX<BadArg>);

编译。

但是,我猜这在语义上与您希望它匹配的内容不匹配。您希望它是 A-ness 而不是您正在测量的 X-ness。

那么您的问题可能不是WorksWithA 相关,而是A 本身。你以为你在哪里添加了THasAnX的要求,但你在哪里没有。

template<HasAnX T>
struct A {
    using X = typename T::X;
};

这使得A 要求T 有一个X。现在,WorksWithA ... 按您的预期工作。

Live example.

【讨论】:

  • “这不需要 T 有一个类型名 X。” 嗯,但我不能实例化 A&lt;BadArg&gt;,因为它会检查 A&lt;BadArg&gt; 中的声明。 . ??
  • 也许我只是对你所说的 "requires"... 感到困惑
  • 我认为您的回答不太正确。例如 int main() { sizeof(A&lt;BadArg&gt;); } 不会编译。它无法编译,因为 A 无法以 BadArg 作为参数进行实例化。所以你的声明“这表明如果它确实有一个类型名 X,那么 A::X 就是那个类型名。”为假,T 必须有一个类型名 X。
【解决方案3】:

这是一个很好(也很正确)的问题,但我认为到目前为止,这个问题一般都没有答案。 让我们假设类模板A 就像:

template<typename T>
class A{
public:
    T variable;
}

这个模板适用于大多数类型,但它不使用T = void 进行实例化,原因很明显。现在让我们假设我们想通过 SFINAE 友好的方法提前找出答案。

判断一个类型是否可实例化的最好工具是检查它是否有大小,现在让我们定义一个类似于std::void_t的工具:

template<size_t...> using void_size_t = void;

现在如果我们定义:

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

template< class T >
struct pre_WorksWithA<T, void_size_t<sizeof(A<T>)>> : std::true_type { };

template<typename T>
constexpr bool WorksWithA = pre_WorksWithA<T>::value;

对于像int 这样的普通类型,结果应该是true。但在void 的情况下,WorksWithApre_WorksWithA 都不能实例化,结果将是编译器出错。

所以如您所见,到目前为止,C++ 中基本上没有任何特性可以帮助我们知道一个类型是否可以实例化而不报错?

【讨论】:

    【解决方案4】:

    你可以用一个简单的概念:

    template<typename T>
    constexpr bool WorksWithA = requires { typename A<T>; };
    

    【讨论】:

    • 如果不是默认可构造的,你可以使用类似requires { typename A&lt;T&gt;; } 的东西。
    • 是否存在任何部分实现足够的 C++20 实现,我们可以用它来验证它的工作原理?
    • clang支持了一段时间
    • 我有clang 10.0.0-4ubuntu1,你知道什么标志吗?
    • -std=c++20-std=c++2a
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多