【问题标题】:Conditional compilation of templates模板的条件编译
【发布时间】:2015-04-06 09:40:30
【问题描述】:

我正在尝试使用 static_assert 来帮助我避免 C++11 中的空指针。

问题似乎是 C++11 要求编译器编译模板,即使它们没有实例化。

我有以下代码:

#include <type_traits>

template<typename T, typename... Us>
std::enable_if_t< std::is_constructible<T, Us...>::value == true, T * >
create_if_constructible(Us... args) { return new T(args...); }

template<typename T, typename... Us>
std::enable_if_t< std::is_constructible<T, Us...>::value == false, T * >
create_if_constructible(Us... args) { 
   static_assert( false, "Class T constructor does not match argument list.");
   return nullptr; 
}

struct ClassA {
   ClassA(int a, string b) {}
};

void foo() {
   ClassA *a = create_if_constructible<ClassA>(1, "Hello");
   // ClassA *b = create_if_constructible<ClassA>(1, "Hello", "world"); // I want compile time error here.
}

我希望这个编译没有错误。但是 static_assert 已编译并给我一个编译时错误。

只有当 ClassA 的第二个实例在代码中时,它才会给我一个编译时错误。

【问题讨论】:

  • 我很困惑 - 即使没有 create_if_constructible,如果您向 ClassA 构造函数提供错误数量的参数,您也会收到编译器错误...您在做什么导致“帮助[你] 避免使用空指针”?
  • 无论如何...如果您将“false”版本中的static_assert() 更改为static_assert(std::is_constructible&lt;T, Us...&gt;::value, "Class T constructor does not match argument list.");,它会执行您想要的操作....
  • @TonyD 我相信这仍然会违反“无法生成有效的专业化”规则,因此仍然是不正确的,不需要诊断(没有一组模板参数可以制作模板签名有效但未触发static_assert)。解决方法很简单——template&lt;class&gt; class always_false : std::false_type {};static_assert 上的 always_false&lt;T&gt;::value。但我也没有看到这段代码的意义。
  • @T.C.:你指的是 14.6/8?我不明白它为什么会适用,而且文本并没有让我清楚地知道需要有一个static_assert 不触发的条件。对我来说,应该根据签名选择模板似乎很直观,然后static_assert 开始。你能分享你对相关性的理解吗?谢谢。
  • @TonyD 问题出在enable_ifis_constructible 一起考虑 - 无论is_constructible&lt;...&gt; 的值如何,要么返回类型无效(如果为真),要么静态断言火灾(如果它是假的)。因此,您无法为其生成有效的专业化。

标签: c++ c++11 static-assert


【解决方案1】:

该标准允许但不要求编译器诊断无法生成有效实例化的模板。这可以从简单的语法错误到static_assert 中的常量false 表达式的示例。 §14.6 [temp.res]/p8:

如果无法为模板生成有效的特化,并且 模板未实例化,模板格式不正确,否 需要诊断。

不过,我对所有这些 SFINAE 机制感到困惑。一个简单的

template<typename T, typename... Us>
T* create_if_constructible(Us... args) { return new T(args...); }

如果 T 不能从给定的参数构造,则已经拒绝编译,所以我不确定这种复杂的迂回语句将如何帮助您“避免空指针”。

无论如何,使选择第二个函数模板成为编译时错误的一种简单方法是显式删除它。

template<typename T, typename... Us>
std::enable_if_t< std::is_constructible<T, Us...>::value == false, T * >
create_if_constructible(Us... args) = delete;

或者,如果您偏爱static_asserts,可能是因为自定义错误消息,您必须确保理论上有一种方法可以生成模板的有效实例化。这意味着 1) 你的 static_asserting 必须依赖于模板参数,并且 2) 理论上必须有一种方法可以使条件为 true。一个简单的方法是使用辅助模板:

template<class> class always_false : std::false_type {};

template<typename T, typename... Us>
std::enable_if_t< std::is_constructible<T, Us...>::value == false, T * >
create_if_constructible(Us... args) { 
   static_assert( always_false<T>::value, "Class T constructor does not match argument list.");
   return nullptr; 
}

这里的关键点是编译器不能假定always_false&lt;T&gt;::value 总是false,因为后面总是有可能有一个专门化将它设置为true,因此不允许在模板定义时间。

【讨论】:

  • 我同意你在我的帖子中的 cmets,然后我决定删除它并 +1 你的。但是,为了其他人的利益,我想留下其中最好的部分,即reference 进行两阶段名称查找。
【解决方案2】:

在所有的 C++ 标准中,模板都是分两个阶段编译的。第二阶段是实例化,但编译也可能在第一阶段失败。特别是在第一阶段检测到语法错误。

在您的情况下,更简单的解决方案是省略第二个实例化的主体。

另一种解决方案是在static_assert 中使用T,因此编译器必须将评估延迟到阶段2。简单来说:static_assert(sizeof(T)==0,

【讨论】:

  • static_assert(sizeof(T)==0, /*... */); 仍然是格式错误的 NDR。
  • 编译器在忽略函数体时会报错。并且 sizeof(T)==0 仍然会评估函数,即使它没有被使用。
  • @T.C.:那是怎么回事?
  • @user2099460: sizeof 不是函数,也不评估它的参数。 sizeof(*(int*)nullptr) 完全合法。
  • @MSalters 因为没有不会触发该断言的T
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-06-24
  • 1970-01-01
  • 2012-11-03
  • 1970-01-01
  • 2013-11-16
  • 2011-02-08
  • 2011-02-26
相关资源
最近更新 更多