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

c++11 prefered way to limit allowed template argument types

本文关键字:参数 类型 方法 c++11      更新时间:2023-10-16

让我们假设,我有一个模板函数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()中允许的类型上是等效的。是吗?

我要问的是关于";在一个好的代码中应该如何做到这一点;以及什么时候使用什么方法的条件。

感谢您的意见。

为了说明实现目标的不同技术,我对示例进行了重大更改。我希望它仍然保持你问题的精神。

假设我有一个用于存储有理数的类型,并且我希望它可以从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触发编译时错误(并带有您选择的消息),但它不会停止编译:这就是您看到更多消息的原因。

Rational3enable_if的情况使模板对于int以外的类型不可见,因此,它不能进一步检查函数内部的错误,但编译器可以查找其他构造函数,并可能选择另一个。

案例Rational4明确指出,如果我们试图从除int之外的任何其他内容进行转换,则应将其视为硬错误:这对std::is_convertible也是可见的。

(但是,在您的示例中不能使用此技术,因为您将约束到元函数(is_integral)而不是具体类型。)

另一个选项是让编译器只专门处理您需要的类型。

举例说明:通常,您需要为所有模板类和函数的编译器提供实现:

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种不同类型的模板化类。添加新类型有点问题,但它为我节省了很多代码来避免重做。这是一个非常具体的问题,有着非常具体的解决方案。