c ++ 11首选限制允许的模板参数类型的方法

时间:2015-12-02 06:57:09

标签: c++ templates c++11 c++14

让我们假设我有一个模板函数foo(T),我只想接受T的整数类型。所以main.cpp可能看起来像这样:

int main()
{
    int i = 1;
    foo(i); // Should work fine
    foo(&i); // Should not compile
}

现在,有两种方法可以实现这一点,我知道:

1)使用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类型之间的差异产生了大量错误输出。

2)在模板参数列表中使用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()中允许的类型相同。是吗?

我所要求的是关于“如何在良好的代码中完成”以及何时使用何种方法的条件的专家意见。

感谢您的任何意见。

2 个答案:

答案 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种不同类型的模板化类。添加新类型是一个小问题,但它为我节省了大量代码而不重做。这是一个非常具体的解决方案。