尽管 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;
}

当我传入 intunsigned int 时,编译器会抱怨调用不明确,表明它可以使用 Case: DefaultCase: int

这令人困惑,因为 Case: Defaultalways_false static_assert(),我希望编译器不允许它。

我最后一个传递 void* 的示例成功触发了 static_assert() 并导致编译时错误。

我是使用 SFINAE 模板元编程进行编程的新手,所以我怀疑我在 Case: int 专业化中做错了什么

两个问题:

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