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

Enable class constructor in some enumerated template cases

出于性能原因,我使用带有枚举的模板化 class 而不是继承继承(这不是一个选项)。

此时我有类似的东西:

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.

调用不兼容的构造函数,我会在 T 上使用令人讨厌的 ifs/switches 数量,并且会增加异常

我想要的是使用 std::enable_if 或等价物来防止在构造函数上抛出异常并在编译时检测此类错误。

我试过很多堆栈溢出和国外网站的例子std::enable_if,但我几乎无法理解我到底在做什么,而且总是以编译错误告终。

提前致谢,很抱歉提出一个可能回答不多的问题。我对模板一窍不通。

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

Now I'm using an annoying amount of ifs/switches over T and rising exceptions if an incompatible constructor is called for a defined T.

所以你的构造函数似乎不需要对 SFINAE 友好,所以 static_assert 似乎就足够了:

template <QueueType T>
class Queue {
public:
    Queue(int a)
    {
        static_assert(T == A, "!");
        // ...
    }
    Queue(unsigned a, unsigned b)
    {
        static_assert(T == B || T == C, "!");
        // ...
    }
    Queue(somestruct z)
    {
        static_assert(T == B || T == C, "!");
        // ...
    }
    //...
};

静态断言很好 - 但您可以为所有枚举值删除这些构造函数 - 除了您要提供的那个:

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

What I want is to use std::enable_if or equivalent to prevent throwing exceptions on constructor and detect on compilation time such kind of errors.

I've tried many stack-overflows and foreign sites std::enable_if examples, but I can barely understand what I'm really doing and I always end on a compilation error.

std::enable_if(和 SFINAE,更普遍)的问题是它只能检查模板参数。 enable/disable 一个完整的 class 也可以,对 class 的模板参数进行测试,但不能 enable/disable 单个方法,对模板参数进行测试class.

如果你想要 SFINAE enable/disable 一个方法(就像你的构造函数),你必须把它变成一个模板方法并测试方法本身的模板参数。

所以你不能写成

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

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

但是有一个技巧:模板参数可以使用默认的values/types;所以下面的代码有效

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
 }