一种使 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 来回答这个问题。我认为我的问题有点不同,因为我问的正是我的例子,我不确定标准对此的看法。
我认为我的程序不是病式的,当且仅当编译器尝试实例化基本模板时,它才应该编译失败。
我的推理:
[temp.dep.1]:
Inside a template, some constructs have semantics which may differ from one instantiation to another. Such a construct depends on the template parameters.
这应该使 std::is_same<C,T>::value
依赖于 T
。
[temp.res.8.1]:
no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated, or
不适用,因为存在有效的专业化,特别是 OnlyNumbers<C>
是有效的,可以在 class 中使用,例如定义一个成员指针变量(ptr
)。实际上,通过删除断言并取消注释 ptr
、OnlyNumbers<Foo>
代码编译的行。
[temp.res.8.2 - 8.4] 不适用。
- [temp.res.8.5] 我认为这也不适用,但我不能说我完全理解这一部分。
我的问题是:我的推理是否正确?这是使特定 [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(说出你的意思)
假设我们要制作一个模板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
我认为我的程序不是病式的,当且仅当编译器尝试实例化基本模板时,它才应该编译失败。 我的推理:
[temp.dep.1]:
Inside a template, some constructs have semantics which may differ from one instantiation to another. Such a construct depends on the template parameters.
这应该使
std::is_same<C,T>::value
依赖于T
。[temp.res.8.1]:
no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated, or
不适用,因为存在有效的专业化,特别是
OnlyNumbers<C>
是有效的,可以在 class 中使用,例如定义一个成员指针变量(ptr
)。实际上,通过删除断言并取消注释ptr
、OnlyNumbers<Foo>
代码编译的行。[temp.res.8.2 - 8.4] 不适用。
- [temp.res.8.5] 我认为这也不适用,但我不能说我完全理解这一部分。
我的问题是:我的推理是否正确?这是使特定 [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(说出你的意思)