一种使 class 模板特化仅在实例化时无法使用 `static_assert` 进行编译的安全、符合标准的方法?

A safe, standard-compliant way to make a class template specialization fail to compile using `static_assert` only if it is instantiated?

假设我们要制作一个模板class,它只能用数字实例化,否则不能编译。我的尝试:

#include <type_traits>

template<typename T, typename = void>
struct OnlyNumbers{
public:
    struct C{};
    static_assert(std::is_same<C,T>::value, "T is not arithmetic type.");

    //OnlyNumbers<C>* ptr;
};

template<typename T>
struct OnlyNumbers<T, std::enable_if_t<std::is_arithmetic_v<T>>>{};

struct Foo{};
int main()
{
    OnlyNumbers<int>{}; //Compiles
    //OnlyNumbers<Foo>{}; //Error
}

Live demo - All three major compilers seem to work as expected. I'm aware that there is already a similar with answers quoting the standard. The accepted answer uses temp.res.8 together with temp.dep.1 来回答这个问题。我认为我的问题有点不同,因为我问的正是我的例子,我不确定标准对此的看法。

我认为我的程序不是病式的,当且仅当编译器尝试实例化基本模板时,它才应该编译失败。 我的推理:

我的问题是:我的推理是否正确?这是使特定 [class]* 模板无法使用 static_assert 进行编译的安全、符合标准的方式吗**且仅当它被实例化时?

*主要是我对 class 模板感兴趣,欢迎添加函数模板。但是我觉得规则是一样的。

**这意味着没有 T 可以用来从外部实例化模板,就像 T=C 可以从内部使用一样。即使 C 可以以某种方式访问​​,我认为没有办法引用它,因为它会导致这种递归 OnlyNumbers<OnlyNumbers<...>::C>.

编辑:

澄清一下,我知道我可以构造一个表达式,如果其他特化的 none 匹配,则该表达式恰好为假。但这很快就会变得冗长,并且如果专业化发生变化,则很容易出错。

嗯...我不明白你说

是什么意思

[[temp.res.8.1]] Does not apply because there exist a valid specialization, in particular OnlyNumbers is valid and can be used inside the class for e.g. defining a member pointer variable(ptr).

你能给出一个基于OnlyNumbers<C>OnlyNumers有效编译主模板的例子吗?

反正我觉得重点就是这个

如果你问

Is this a safe, standard-compliant way to make a particular [class]* template fail to compile using static_assert if** and only if it is instantiated?

在我看来(可能不包括只有当另一个专业匹配时才正确的测试)答案是 "no" 因为 [temp.res.8.1].

也许您可以打开一扇小门以允许实例化,但只有真正(真的!)想要实例化它的人才能使用。

例如,您可以添加第三个模板参数,具有不同的默认值,如下所示

template<typename T, typename U = void, typename V = int>
struct OnlyNumbers
 {
   static_assert(std::is_same<T, U>::value, "test 1");
   static_assert(std::is_same<T, V>::value, "test 2");
 };

这样你就打开了一扇通向合法实例化的大门

OnlyNumbers<Foo, Foo, Foo>     o1;
OnlyNumbers<void, void, void>  o2;
OnlyNumbers<int, int>          o3;

但仅说明至少第二种模板类型。

无论如何,你为什么不简单地避免定义模板的主版本?

// declared but (main version) not defined
template<typename T, typename = void>
struct OnlyNumbers;

// only specialization defined
template<typename T>
struct OnlyNumbers<T, std::enable_if_t<std::is_arithmetic_v<T>>>
 { };

静态断言可以直接在 class 中使用,无需做任何复杂的事情。

#include <type_traits>

template<typename T>
struct OnlyNumbers {
    static_assert(std::is_arithmetic_v<T>, "T is not arithmetic type.");
    // ....
};

在某些情况下,您可能会收到其他错误消息,因为为 non-arithmetic 类型实例化 OnlyNumbers 可能会导致更多编译错误。

我不时使用的一个技巧是

#include <type_traits>

template<typename T>
struct OnlyNumbers {
    static_assert(std::is_arithmetic_v<T>, "T is not arithmetic type.");
    using TT = std::conditional_t<std::is_arithmetic_v<T>,T,int>;
    // ....
};

在这种情况下,您的 class 实例化为有效类型 int。由于静态断言无论如何都会失败,因此不会产生负面影响。

您的代码是 ill-formed,因为无法实例化主模板。请参阅 Barry 对您链接到的相关问题的回答中的标准引述。您用来确保明确规定的标准要求 cannot 得到满足的迂回方式无济于事。停止与你的编译器对抗。标准,并采用 Handy999 的方法。如果您仍然不想这样做,例如出于 DRY 的原因,实现目标的一致方法是:

template<typename T, typename Dummy = void>
struct OnlyNumbers{
public:
    struct C{};
    static_assert(! std::is_same<Dummy, void>::value, "T is not a number type.");

两条备注:

  • 首先,我故意替换了错误信息,因为错误信息"is not an arithmetic type" 尖叫着说你必须测试! std::is_arithmetic<T>::value。如果您对 "numeric" 类型有多个重载,我概述的方法可能是有意义的,其中一些满足标准的算术类型要求,而另一些可能不满足(例如,可能是来自多精度库的类型)。
  • 其次,您可能会反对有人可以写例如OnlyNumbers<std::string, int> 打败静态断言。我要说的是,那是他们的问题。请记住,每当你做出一些白痴证明时,大自然就会成为一个更好的白痴。 ;-) 说真的, 使 API 易于使用且难以滥用,但你无法解决精神错乱,也不应该费心尝试。

TL;DR:KISS 和 SWYM(说出你的意思)