让我们假设我有一个模板函数foo(T)
,我只想接受T
的整数类型。所以main.cpp
可能看起来像这样:
int main()
{
int i = 1;
foo(i); // Should work fine
foo(&i); // Should not compile
}
现在,有两种方法可以实现这一点,我知道:
static_assert
#include <type_traits>
template<typename T> void foo(T value) {
static_assert(std::is_integral<T>::value, "Not integral!");
// Logic goes here
}
此选项的优点是,我可以指定错误消息。
缺点是我的g ++ 4.9.3确实为我的T
类型和// Logic
类型之间的差异产生了大量错误输出。
std::enable_if
#include<type_traits>
template<typename T, class = typename std::enable_if<std::is_integral<T>::value>::type>
void foo(T value) {
// Logic goes here
}
缺点是产生的错误输出不能很好地描述问题,优点是它的数量少得多。 (通过组合两种方法可以减少错误输出量。)
为了完整性,还有其他(好的)方法可以达到相同的效果吗?
什么选项应该减少编译时间?
据我所知,这两种方法在foo()
中允许的类型相同。是吗?
我所要求的是关于“如何在良好的代码中完成”以及何时使用何种方法的条件的专家意见。
感谢您的任何意见。
答案 0 :(得分:1)
为了说明实现目标的不同技巧,我已经大大改变了这个例子。我希望它仍然保留你的问题的精神。
假设我有一个用于存储有理数的类型,我希望它可以从int
隐式转换:
struct Rational1
{
int i_;
Rational1(int i) : i_(i) {}
};
现在,它有效,但现在(因为隐式转换)它也可以从double
转换而且我们不想要它:
Rational1 r = 2.5; // BUG (will be interpreted as 2.0)
我可以想到三种不同的预防方式(你已经提到过2):
struct Rational2
{
int i_;
template <typename T>
Rational2(T i) : i_(i)
{ static_assert(std::is_same<T, int>::value, "msg"); }
};
struct Rational3
{
int i_;
template <typename T, typename std::enable_if<std::is_same<T, int>::value, int>::type = 0>
Rational3(T i) : i_(i) {}
};
struct Rational4
{
int i_;
Rational4(int i) : i_(i) {}
template <typename T>
Rational4(T i) = delete;
};
测试它:
Rational2 r = 2.5; // compile-time error
Rational3 r = 2.5; // compile-time error
Rational4 r = 2.5; // compile-time error
但是,如果您使用std::is_convertible
查看结果不同:
static_assert(std::is_convertible<double, Rational2>::value = true, "");
static_assert(std::is_convertible<double, Rational3>::value = false, "");
static_assert(std::is_convertible<double, Rational4>::value = false, "");
这表明Rational2
是可转换形式double
:选择并编译构造函数模板。该标准要求static_assert
内部触发编译时错误(以及您选择的消息),但它不会停止编译:这就是您看到更多消息的原因。
Rational3
的案例enable_if
使模板对int
以外的类型不可见,因此,它无法进一步检查函数内部的错误,但编译器可以查找其他构造函数,以及也许选择另一个。
案例Rational4
明确指出,如果我们尝试转换除int
以外的任何其他内容,则应将其视为硬错误:std::is_convertible
也可以看到此错误。
(但是你不能在你的例子中使用这种技术,你将约束为元函数(is_integral
)而不是具体类型。)
答案 1 :(得分:1)
另一个选择是让编译器专门针对你需要的类型。
使用示例解释: 通常,您需要为所有模板类和函数使编译器可用:
template<typename T>
class MyClass {
public:
MyClass(T arg) { d_val = arg; }
privateL
T d_val;
};
因此,以下内容将起作用,因为编译器将能够实现所有这些:
MyClass<double> classDouble;
MyCalss<int> classInt;
但是如果你不提供实现,那么编译器就无法实现它们,所以你可以这样做:
<强> MyClass.h 强>
template<typename T>
class MyClass {
public:
MyClass(T arg);
privateL
T d_val;
};
<强> MyClass.cpp 强>
template<typename T>
MyClass::MyClass() { d_val = arg; }
/* The explicitly make the one for int */
MyClass<int> tmpInt;
使用:
MyCalss<int> classInt; // Will compile since the compiler already generated the code when it compiled the cpp file
MyClass<double> classDouble; // Wont compile since the compiler does not know how to produce the code.
此方法的问题是您需要在源文件(cpp)中指定所需的每种类型。
我将此方法用于16种不同类型的模板化类。添加新类型是一个小问题,但它为我节省了大量代码而不重做。这是一个非常具体的解决方案。