模板的条件编译

Conditional compilation of templates

我正在尝试让 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 的第二次实例化在代码中时,它才会给我一个编译时错误。

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

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

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

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

If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic required.

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

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

如果 T 不能从给定的参数构造,则已经拒绝编译,所以我不确定这种复杂的迂回表述对你有何帮助 "avoid null pointers"。

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

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_assert 所依赖的必须取决于模板参数,并且 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<T>::value 总是 false 因为后面总是有一个专门化将它设置为 true,所以不允许在模板定义时拒绝它。