Включить конструктор класса в некоторых перечисляемых шаблонах - PullRequest
0 голосов
/ 06 ноября 2018

Из соображений производительности я использую шаблонный класс с перечислением вместо реле наследования (это не вариант).

На данный момент у меня есть что-то вроде:

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
}

Теперь я использую раздражающее количество переключений if / / T и растущих исключений, если для определенного T.

вызывается несовместимый конструктор.

То, что я хочу, это использовать std::enable_if или его эквивалент, чтобы предотвратить появление исключений в конструкторе и обнаруживать во время компиляции такие ошибки.

Я перепробовал много переполнений стека и сторонних сайтов std::enable_if примеров, но я с трудом понимаю, что я на самом деле делаю, и всегда заканчиваю ошибкой компиляции.

Заранее спасибо и извините за вопрос, который, вероятно, тривиально ответил. Я нуб с шаблонами.

Среда : Linux GCC 8 и c ++ 14 Ограничения : максимальная производительность без виртуальных методов.

Ответы [ 3 ]

0 голосов
/ 06 ноября 2018

Статические утверждения в порядке - но вы можете удалить эти конструкторы для всех значений перечисления - кроме того, который вы хотите предоставить:

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) {}

Одно большое преимущество перед sulution с 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");
    ...
}

Демонстрационная версия

0 голосов
/ 06 ноября 2018

То, что я хочу, это использовать 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, которое является параметром шаблона конструктора.

Чтобы включить второй конструктор, только если T равен B или C, вы можете написать

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
 }
0 голосов
/ 06 ноября 2018

Теперь я использую раздражающее количество ifs / переключателей T и возрастающих исключений, если для определенного 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, "!");
        // ...
    }
    //...
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...