尽管 static_assert 默认模板匹配
Default template is matching despite static_assert
我正在尝试创建一个模板化函数,该函数在编译时被强制只使用特化。我引用了 ,它建议在继承自 std::false_type
的东西上使用 static_assert
。
#include <iostream>
using namespace std;
template<typename T>
struct always_false : std::false_type {};
//Case: Default
template<typename T>
void foo(T val) {
static_assert(always_false<T>::value, "");
}
//Case: bool
template<>
void foo<bool>(bool val) {
cout << "Is explicitly a bool! " << val << endl;
}
//Case: int
template<typename T, typename std::enable_if<!std::is_same<T,bool>::value && std::is_convertible<T,int>::value,int>::type=0>
void foo(T val) {
cout << "Can be implicitly converted to int! " << (int)val << endl;
}
int main() {
foo(true); //(Good) Works correctly
foo((int)5); //(Bad) Error: call of overload foo(int) is ambiguous
foo((unsigned int)10); //(Bad) Error: call of overload foo(unsigned int) is ambiguous
foo((void*)nullptr); //(Good) Error: static assertion failed
return 0;
}
当我传入 int
或 unsigned int
时,编译器会抱怨调用不明确,表明它可以使用 Case: Default
或 Case: int
。
这令人困惑,因为 Case: Default
有 always_false
static_assert()
,我希望编译器不允许它。
我最后一个传递 void*
的示例成功触发了 static_assert()
并导致编译时错误。
我是使用 SFINAE 模板元编程进行编程的新手,所以我怀疑我在 Case: int
专业化中做错了什么
两个问题:
- 为什么这段代码中的
foo(int)
有歧义?
- 有没有更好的使用方法
获得此所需行为的模板(显式
bool
专业化 + 隐式整数专业化)?
Why is foo(int) in this code ambiguous?
因为选择static_assert()
的版本会报错但仍然存在;所以编译器不知道是选择通用版本还是启用整数的版本。
Is there a better way to use templates to get this desired behavior (explicit bool specialization + implicit int specialization)?
一种可能的方法是避免使用通用版本,SFINAE 启用您需要的版本
以下是完整的工作示例
#include <iostream>
#include <type_traits>
template <typename T>
typename std::enable_if<std::is_same<T, bool>::value>::type foo(T val)
{ std::cout << "bool case " << val << std::endl; }
template <typename T>
typename std::enable_if< ! std::is_same<T, bool>::value
&& std::is_convertible<T, int>::value>::type foo(T val)
{ std::cout << "integer case " << (int)val << std::endl; }
int main()
{
foo(true); // bool case
foo(1); // integer case
foo(2U); // integer case
foo(3L); // integer case
foo(4UL); // integer case
foo(5LL); // integer case
foo(6ULL); // integer case
// foo((void*)nullptr); // compilation error
}
-- 编辑--
OP
Sorry, I am still confused. Could you elaborate? I thought that due to SFINAE, that if an error occurred in substitution, it would use the other template.
没错。
问题是 没有 替换错误,编译器必须在同一模板的两个不同版本之间进行选择。
我的意思是:在您的示例中,当您调用 foo(5)
时,替换
没有错误
typename std::enable_if<!std::is_same<T,bool>::value
&& std::is_convertible<T,int>::value,int>::type=0>
所以编译器必须在两个模板函数之间做出选择
template<typename T>
void foo(T val) {
static_assert(always_false<T>::value, "");
}
//Case: int
template<typename T, int = 0>
void foo(T val) {
cout << "Can be implicitly converted to int! " << (int)val << endl;
}
仅模板值与默认值不同,因此(从编译器的角度)无法区分。
观察
template<>
void foo<bool>(bool val) {
cout << "Is explicitly a bool! " << val << endl;
}
是一个(完整的)模板专业化但是
//Case: int
template<typename T, int = 0>
void foo(T val) {
cout << "Can be implicitly converted to int! " << (int)val << endl;
}
不是模板特化(C++11/14/17 中不允许函数的部分模板特化;您只能部分特化 structs/classes);是通用模板。
您可以按照@max66 的建议使用 SFINAE,但是您的用例的一种简单方法是使用 bool
重载和模板化版本
void foo(bool);
template <class T>
void foo(T);
您可以强制将 T
转换为 int
(static_assert
),但在大多数情况下没有必要,因为 foo
的 body在这种情况下可能会是 ill-formed,从而导致 compile-time 错误。
template <class T>
void foo(T) {
static_assert(std::is_convertible<T, int>::value, "");
}
结合你的例子:
foo(true); // foo(bool) is chosen because it is the best match
foo((int)5); // foo<int>(int) is chosen, the assertion passes
foo((unsigned int)10); // foo<unsigned int>(unsigned int) is chosen, assertion ok
foo((void*)nullptr); // foo<void*>(void*) is chosen, the assertion fails
我正在尝试创建一个模板化函数,该函数在编译时被强制只使用特化。我引用了 std::false_type
的东西上使用 static_assert
。
#include <iostream>
using namespace std;
template<typename T>
struct always_false : std::false_type {};
//Case: Default
template<typename T>
void foo(T val) {
static_assert(always_false<T>::value, "");
}
//Case: bool
template<>
void foo<bool>(bool val) {
cout << "Is explicitly a bool! " << val << endl;
}
//Case: int
template<typename T, typename std::enable_if<!std::is_same<T,bool>::value && std::is_convertible<T,int>::value,int>::type=0>
void foo(T val) {
cout << "Can be implicitly converted to int! " << (int)val << endl;
}
int main() {
foo(true); //(Good) Works correctly
foo((int)5); //(Bad) Error: call of overload foo(int) is ambiguous
foo((unsigned int)10); //(Bad) Error: call of overload foo(unsigned int) is ambiguous
foo((void*)nullptr); //(Good) Error: static assertion failed
return 0;
}
当我传入 int
或 unsigned int
时,编译器会抱怨调用不明确,表明它可以使用 Case: Default
或 Case: int
。
这令人困惑,因为 Case: Default
有 always_false
static_assert()
,我希望编译器不允许它。
我最后一个传递 void*
的示例成功触发了 static_assert()
并导致编译时错误。
我是使用 SFINAE 模板元编程进行编程的新手,所以我怀疑我在 Case: int
专业化中做错了什么
两个问题:
- 为什么这段代码中的
foo(int)
有歧义? - 有没有更好的使用方法
获得此所需行为的模板(显式
bool
专业化 + 隐式整数专业化)?
Why is foo(int) in this code ambiguous?
因为选择static_assert()
的版本会报错但仍然存在;所以编译器不知道是选择通用版本还是启用整数的版本。
Is there a better way to use templates to get this desired behavior (explicit bool specialization + implicit int specialization)?
一种可能的方法是避免使用通用版本,SFINAE 启用您需要的版本
以下是完整的工作示例
#include <iostream>
#include <type_traits>
template <typename T>
typename std::enable_if<std::is_same<T, bool>::value>::type foo(T val)
{ std::cout << "bool case " << val << std::endl; }
template <typename T>
typename std::enable_if< ! std::is_same<T, bool>::value
&& std::is_convertible<T, int>::value>::type foo(T val)
{ std::cout << "integer case " << (int)val << std::endl; }
int main()
{
foo(true); // bool case
foo(1); // integer case
foo(2U); // integer case
foo(3L); // integer case
foo(4UL); // integer case
foo(5LL); // integer case
foo(6ULL); // integer case
// foo((void*)nullptr); // compilation error
}
-- 编辑--
OP
Sorry, I am still confused. Could you elaborate? I thought that due to SFINAE, that if an error occurred in substitution, it would use the other template.
没错。 问题是 没有 替换错误,编译器必须在同一模板的两个不同版本之间进行选择。
我的意思是:在您的示例中,当您调用 foo(5)
时,替换
typename std::enable_if<!std::is_same<T,bool>::value
&& std::is_convertible<T,int>::value,int>::type=0>
所以编译器必须在两个模板函数之间做出选择
template<typename T>
void foo(T val) {
static_assert(always_false<T>::value, "");
}
//Case: int
template<typename T, int = 0>
void foo(T val) {
cout << "Can be implicitly converted to int! " << (int)val << endl;
}
仅模板值与默认值不同,因此(从编译器的角度)无法区分。
观察
template<>
void foo<bool>(bool val) {
cout << "Is explicitly a bool! " << val << endl;
}
是一个(完整的)模板专业化但是
//Case: int
template<typename T, int = 0>
void foo(T val) {
cout << "Can be implicitly converted to int! " << (int)val << endl;
}
不是模板特化(C++11/14/17 中不允许函数的部分模板特化;您只能部分特化 structs/classes);是通用模板。
您可以按照@max66 的建议使用 SFINAE,但是您的用例的一种简单方法是使用 bool
重载和模板化版本
void foo(bool);
template <class T>
void foo(T);
您可以强制将 T
转换为 int
(static_assert
),但在大多数情况下没有必要,因为 foo
的 body在这种情况下可能会是 ill-formed,从而导致 compile-time 错误。
template <class T>
void foo(T) {
static_assert(std::is_convertible<T, int>::value, "");
}
结合你的例子:
foo(true); // foo(bool) is chosen because it is the best match
foo((int)5); // foo<int>(int) is chosen, the assertion passes
foo((unsigned int)10); // foo<unsigned int>(unsigned int) is chosen, assertion ok
foo((void*)nullptr); // foo<void*>(void*) is chosen, the assertion fails