尽管static_assert,默认模板仍然匹配

时间:2017-09-20 12:37:35

标签: c++ c++11 templates sfinae template-specialization

我正在尝试创建一个模板化函数,强制执行编译时只使用特化。我引用了Force a compile time error in a template specialization,建议对从static_assert继承的内容使用std::false_type

#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专业化中做错了什么

两个问题:

  • 为什么此代码中foo(int)不明确?
  • 有更好的使用方法吗? 模板来获得这种期望的行为(显式bool特化+隐式整数特化)?

2 个答案:

答案 0 :(得分:1)

  

为什么这段代码中的foo(int)不明确?

因为带有static_assert()的版本在选中但仍然存在时会出错;因此,编译器不知道是选择通用版本还是启用整数版本。

  

有没有更好的方法来使用模板来获得所需的行为(显式bool特化+隐式int特化)?

一种可能的方法是避免使用通用版本,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

  

抱歉,我仍然感到困惑。你能详细说说吗?我认为由于SFINAE,如果在替换中发生错误,它将使用另一个模板。

完全。 问题是当没有替换错误时,编译器必须在同一模板的两个不同版本之间进行选择。

我的意思是:在您的示例中,当您致电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中不允许对函数进行部分模板特化;您可以仅部分地专门化结构/类);是一个通用模板。

答案 1 :(得分:0)

你可以按照@ max66的建议使用SFINAE,但对你的用例来说,一个简单的方法就是bool重载和模板化版本

void foo(bool);

template <class T>
void foo(T);

您可以强制T可转换为intstatic_assert)但在大多数情况下没有必要,因为foo的正文可能会格式不正确在这种情况下,从而导致编译时错误。

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