【问题标题】:Default template is matching despite static_assert尽管 static_assert 默认模板匹配
【发布时间】:2017-09-20 12:37:34
【问题描述】:

我正在尝试创建一个模板化函数,它在编译时强制使用只使用特化。我引用了Force a compile time error in a template specialization,它建议在继承自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 专业化中做错了什么

两个问题:

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

【问题讨论】:

  • 您不能声明模板特化,即使是对其实例化的任何类型都无效的主要特化。无需诊断。

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


【解决方案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 中不允许函数的部分模板特化;您只能部分特化结构/类);是一个通用模板。

【讨论】:

  • "因为带有 static_assert() 的版本如果选择了会报错但仍然存在;所以编译器不知道是选择通用版本还是启用整数的版本。"对不起,我还是一头雾水。你能详细说明一下吗?我认为由于 SFINAE,如果替换发生错误,它将使用另一个模板。
  • 感谢您的解决方案,我能够让它工作。快速跟进 - 我对何时需要定义默认模板然后进行专门化感到困惑,而我可以只声明没有默认模板的专门版本。
  • @Ross - 答案改进;希望这会有所帮助。
【解决方案2】:

您可以按照@max66 的建议使用 SFINAE,但是对于您的用例,一种简单的方法是使用 bool 重载和模板版本

void foo(bool);

template <class T>
void foo(T);

您可以强制 T 可转换为 int (static_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

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多