Подтвердить что ты не робот

Конструкторы, шаблоны и непиковые параметры

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

Здесь возникли проблемы.
Может быть, я что-то упустил, но я не вижу простого способа предоставить такой параметр конструктору, потому что он не может быть выведен или явно указан.

До сих пор я нашел следующие альтернативы:

  • поместите указанный выше параметр в список параметров класса

  • создайте метод factory или factory, который можно вызвать в качестве примера как factory<42>(params)

  • предоставляет конструкцию признаков конструктору

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

#include<iostream>
#include<array>

template<int N>
struct traits {
    static constexpr int size = N;
};

class C final {
    struct B {
        virtual ~B() = default;
        virtual void foo() = 0;
    };

    template<int N>
    struct D: public B{
        void foo() {
            using namespace std;
            cout << N << endl;
        }

        std::array<int, N> arr;
    };

 public:
     template<typename T>
     explicit C(T) {
         b = new D<T::size>{};
     }

     ~C() { delete b; }

     void foo() { b->foo(); }

 private:
     B *b;
};

int main() {
    C c{traits<3>{}};
    c.foo();
}

Если честно, ни одно из вышеперечисленных решений не подходит хорошо:

  • перемещение параметра в список параметров класса полностью разрушает его конструкцию и не является жизнеспособным решением

  • a factory метод - это то, чего я бы хотел избежать, но он мог решить проблему

  • структура признаков кажется лучшим решением до сих пор, но почему-то я не полностью удовлетворен

Вопрос довольно прост: есть ли что-то, что я пропустил там, возможно, более легкое, более элегантное решение, деталь языка, который я полностью забыл, или три подхода, упомянутых выше тех, из которых я должен выбрать? < ш > Любое предложение будет оценено.

4b9b3361

Ответ 1

Вы должны передать то, что можно вывести. Простейшая вещь для использования - это просто пустая оболочка для int: std::integral_constant. Поскольку вы только хотите int, я верю, мы можем его псевдоним, а затем принимать только этот конкретный тип:

template <int N>
using int_ = std::integral_constant<int, N>;

Если ваш конструктор C просто принимает это:

 template <int N>
 explicit C(int_<N> ) {
     b = new D<N>{};
 }

 C c{int_<3>{}};

Вы можете даже изо всех сил и создать для этого пользовательский литерал (a la Boost.Hana), чтобы вы могли написать:

auto c = 3_c; // does the above

Кроме того, рассмотрите возможность просто переслать признак до D. Метапрограммирование работает лучше, если все везде есть тип. То есть, все же принимайте те же int_ в C:

template <class T>
explicit C(T ) {
    b = new D<T>{};
}

Где теперь D ожидает что-то, что имеет ::value:

template <class T>
struct D: public B{
    static constexpr int N = T::value;

    void foo() {
        using namespace std;
        cout << N << endl;
    }

    std::array<int, N> arr;
};

Это то же самое в любом случае от пользователя перспективы C, но просто стоит подумать.

Ответ 2

Я думаю, что решение с "признаками" является лучшим для большинства случаев.

Только чтобы сделать немного "беспорядок" в этой проблеме, я предоставлю еще две альтернативы. Возможно, в некоторых очень конкретных случаях - они могут быть в некотором роде лучше.


  • Глобальная переменная шаблона - вы можете назвать ее прототипом :

class C только в своем конструкторе отличается от вашего исходного кода:

class C final {
    // All B and D defined as in OP code
 public:
    // Here the change - c-tor just accepts D<int> 
    template <int size>
    explicit C(D<size>* b) : b(b) {}

    // all the rest as in OP code
};

Прототип - глобальная переменная шаблона:

template <int N>
C c{new C::D<N>()}; 
// this variable should be rather const - but foo() is not const 
// and copy semantic is not implemented...

И использование:

int main() {
    // you did not implement copy semantic properly - so just reference taken
    C& c = ::c<3>; 
    c.foo();
}

  1. Решение с базовым классом - и получить класс в зависимости от int

Это решение, хотя выглядит довольно многообещающим, я бы лично избегал - это только усложняло дизайн, и здесь также присутствует некоторая возможность нарезки объектов.

class CBase {
    // all here as in OP code for C class
public:
    // only difference is this constructor:
    template<int size>
    explicit CBase(D<size>* b) : b(b) {}
};

Затем - окончательный класс:

template <int N>
class C final : private CBase {
public:
    C() : CBase(new CBase::D<N>()) {}
    using CBase::foo;
};

Использование:

int main() {
    C<3> c;
    c.foo();
}

Q: Можно спросить, в каком смысле решение с базовым классом лучше, чем просто добавить int в качестве другого параметра.
A: по базовому классу реализации вам не нужно иметь много "копий" одного и того же кода - вы избегаете шаблона кода bloat...

Ответ 3

используйте специализированную специализацию и наследование:

#include <iostream>
using namespace std;

template <int num> struct A {
    A() { cout << "generic" << endl; }
};

template <> struct A<1> {
    A() { cout << "implementation of 1" << endl; }
};

template <int num>
struct B : public A<num> {
    B() : A<num>() {}
};

int main(int argc, char *argv[])
{
    B<1> b;
    B<3> b1;
    B<4> b2;
}

изменить:, или вы можете сделать это еще проще:

template <int num>
struct A {
    A();
};

template <int num>
A<num>::A() { cout << "general " << num << endl; }

template <>
A<1>::A() { cout << "specialization for 1" << endl; }