在某些枚举的模板情况下启用类构造函数

时间:2018-11-06 15:36:35

标签: c++ templates c++14 sfinae enable-if

出于性能方面的考虑,我使用带有枚举的模板化类,而不是继承继承(这不是一个选择)。

在这一点上,我有类似的东西:

typedef enum { A, B, C, D } QueueType;

template <QueueType T> class Queue {
    Queue(int a){...} // only usable when T = A
    Queue(unsigned a, unsigned b){...} // only usable when T = B || T = C
    Queue(somestruct z){...} // only usable when T = B || T = C
    //other constructors
}

现在,我在T上使用大量烦人的ifs / switchs,如果为已定义的T调用了不兼容的构造函数,则会出现异常。

我想要的是使用std::enable_if或等效名称来防止在构造函数上引发异常并在编译时检测此类错误。

我已经尝试了很多堆栈溢出和外部站点std::enable_if的示例,但是我几乎无法理解我在做什么,而且我总是以编译错误告终。

在此先感谢您,并很抱歉提出了一个可能很简单的问题。我对模板没有知识。

环境:Linux GCC 8和c ++ 14 限制:没有虚拟方法的最高性能。

3 个答案:

答案 0 :(得分:6)

  

我想要的是使用std :: enable_if或等效方法来防止在构造函数上引发异常并在编译时检测此类错误。

     

我已经尝试了很多堆栈溢出和外部站点std::enable_if的示例,但是我几乎无法理解我在做什么,而且我总是以编译错误告终。

std::enable_if(以及SFINAE,更常见的问题)的问题在于它只能检查模板参数。因此,可以通过对类的模板参数进行测试来启用/禁用完整的类,而不能通过对类的模板参数进行测试来启用/禁用单个方法。

如果要SFINAE启用/禁用方法(如构造函数),则必须使其成为模板方法并测试该方法本身的模板参数。

所以你不能写东西

template <typename = std::enable_if_t<T == A>>
Queue (int)
 { } // only usable when T = A

因为T是类的模板参数,而不是构造函数的模板参数。

但是有一个窍门:您可以为模板参数使用默认值/类型;因此以下代码有效

template <QueueType U = T, typename = std::enable_if_t<U == A>>
Queue (int)
 { } // only usable when T = A 

因为检查了值U,它是构造函数的模板参数。

要仅在TBC时启用第二个构造函数,可以编写

template <QueueType U = T, typename = std::enable_if_t<(U == B) || (U == C)>> 
Queue (unsigned, unsigned)
 { } // only usable when T = B || T = C

以下是完整的编译示例

#include <type_traits>

typedef enum { A, B, C, D } QueueType;

template <QueueType T>
struct Queue
 {
   template <QueueType U = T, typename = std::enable_if_t<U == A>>
   Queue (int)
    { } // only usable when T = A

   template <QueueType U = T, typename = std::enable_if_t<(U == B) || (U == C)>>
   Queue (unsigned, unsigned)
    { } // only usable when T = B || T = C
 };

int main()
 {
   Queue<A>  qa0{1};         // compile
   //Queue<A>  qa1{1u, 2u};  // compilation error

   // Queue<B>  qb0{1};      // compilation error
   Queue<B>  qb1{1u, 2u};    // compile

   // Queue<C>  qc0{1};      // compilation error
   Queue<C>  qc1{1u, 2u};    // compile

   // Queue<D>  qd0{1};      // compilation error
   // Queue<D>  qd1{1u, 2u}; // compilation error
 }

答案 1 :(得分:3)

  

现在我正在使用大量烦人的if / t切换,如果为定义的T调用了不兼容的构造函数,则会出现异常。

所以看来您不需要构造函数对SFINAE友好,因此<__main__.bag object at 0x004D0830> ['pencil', 'book'] <__main__.bag object at 0x004D0790> ['pencil', 'book'] 似乎足够:

static_assert

答案 2 :(得分:2)

静态断言没问题-但您可以删除所有枚举值的这些构造函数-除了要提供的值:

template <QueueType T> 
class Queue 
{
public:    
    Queue(int a) = delete; // only usable when T = A
    //other constructors
    Queue(unsigned a, unsigned b) = delete; // only usable when T = B || T = C
    Queue(somestruct z) = delete; // only usable when T = B || T = C

private:
    // not necessary - but allows to have a little less code
    struct EnablerType {};
    static constexpr EnablerType Enabler{};
    Queue(unsigned a, unsigned b, EnablerType) { }// only usable when T = B || T = C
    Queue(somestruct z, EnablerType) { } // only usable when T = B || T = C

};

现在-显式启用:

template <>
inline Queue<A>::Queue(int a) {}
template <>
inline Queue<B>::Queue(unsigned a, unsigned b) : Queue(a, b, Enabler) {}
template <>
inline Queue<C>::Queue(unsigned a, unsigned b) : Queue(a, b, Enabler) {}
template <>
inline Queue<B>::Queue(somestruct z) : Queue(z, Enabler) {}
template <>
inline Queue<C>::Queue(somestruct z) : Queue(z, Enabler) {}

static_assert相比,一大优势是您可以检查Queue是否由给定的参数集构造而成(因此您可以进行进一步的SFINAE):

int main() {
    static_assert(std::is_constructible_v<Queue<A>, int>, "failed");
    static_assert(!std::is_constructible_v<Queue<B>, int>, "failed");
    ...
}

Live demo